/* * Copyright (C) 2011-2016 Daniel Scharrer * * This software is provided 'as-is', without any express or implied * warranty. In no event will the author(s) be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include #include #include #include #include #include #include #include "release.hpp" #include "cli/extract.hpp" #include "setup/version.hpp" #include "util/console.hpp" #include "util/log.hpp" #include "util/time.hpp" #include "util/windows.hpp" namespace fs = boost::filesystem; namespace po = boost::program_options; enum ExitValues { ExitSuccess = 0, ExitUserError = 1, ExitDataError = 2 }; static const char * get_command(const char * argv0) { if(!argv0) { argv0 = innoextract_name; } std::string var = argv0; #ifdef _WIN32 size_t pos = var.find_last_of("/\\"); #else size_t pos = var.find_last_of('/'); #endif if(pos != std::string::npos) { var = var.substr(pos + 1); } var += "_COMMAND"; const char * env = std::getenv(var.c_str()); if(env) { return env; } else { return argv0; } } static void print_version(const extract_options & o) { if(o.silent) { std::cout << innoextract_version << '\n'; return; } std::cout << color::white << innoextract_name << ' ' << innoextract_version << color::reset #ifdef DEBUG << " (with debug output)" #endif << '\n'; if(!o.quiet) { std::cout << "Extracts installers created by " << color::cyan << innosetup_versions << color::reset << '\n'; } } static void print_help(const char * name, const po::options_description & visible) { std::cout << color::white << "Usage: " << name << " [options] \n\n" << color::reset; std::cout << "Extract files from an Inno Setup installer.\n"; std::cout << "For multi-part installers only specify the exe file.\n"; std::cout << visible << '\n'; std::cout << "Extracts installers created by " << color::cyan << innosetup_versions << color::reset << '\n'; std::cout << '\n'; std::cout << color::white << innoextract_name << ' ' << innoextract_version << color::reset << ' ' << innoextract_copyright << '\n'; std::cout << "This is free software with absolutely no warranty.\n"; } static void print_license() { std::cout << color::white << innoextract_name << ' ' << innoextract_version << color::reset << ' ' << innoextract_copyright << '\n'; std::cout << '\n'<< innoextract_license << '\n'; ; } int main(int argc, char * argv[]) { po::options_description generic("Generic options"); generic.add_options() ("help,h", "Show supported options") ("version,v", "Print version information") ("license", "Show license information") ; po::options_description action("Actions"); action.add_options() ("test,t", "Only verify checksums, don't write anything") ("extract,e", "Extract files (default action)") ("list,l", "Only list files, don't write anything") ("list-languages", "List languages supported by the installer") ("gog-game-id", "Determine the GOG.com game ID for this installer") ; po::options_description modifiers("Modifiers"); modifiers.add_options() ("collisions", po::value(), "How to handle duplicate files") ("default-language", po::value(), "Default language for renaming") ("dump", "Dump contents without converting filenames") ("lowercase,L", "Convert extracted filenames to lower-case") ("timestamps,T", po::value(), "Timezone for file times or \"local\" or \"none\"") ("output-dir,d", po::value(), "Extract files into the given directory") ("gog,g", "Extract additional archives from GOG.com installers") ; po::options_description filter("Filters"); filter.add_options() ("exclude-temp,m", "Don't extract temporary files") ("language", po::value(), "Extract only files for this language") ("language-only", "Only extract language-specific files") ("include,I", po::value< std::vector >(), "Extract only files that match this path") ; po::options_description io("Display options"); io.add_options() ("quiet,q", "Output less information") ("silent,s", "Output only error/warning information") ("no-warn-unused", "Don't warn on unused .bin files") ("color,c", po::value()->implicit_value(true), "Enable/disable color output") ("progress,p", po::value()->implicit_value(true), "Enable/disable the progress bar") #ifdef DEBUG ("debug", "Output debug information") #endif ; po::options_description hidden("Hidden options"); hidden.add_options() ("setup-files", po::value< std::vector >(), "Setup files to be extracted") /**/; po::options_description options_desc; options_desc.add(generic).add(action).add(modifiers).add(filter).add(io).add(hidden); po::options_description visible; visible.add(generic).add(action).add(modifiers).add(filter).add(io); po::positional_options_description p; p.add("setup-files", -1); po::variables_map options; // Parse the command-line. try { po::store(po::command_line_parser(argc, argv).options(options_desc).positional(p).run(), options); po::notify(options); } catch(po::error & e) { color::init(color::disable, color::disable); // Be conservative std::cerr << "Error parsing command-line: " << e.what() << "\n\n"; print_help(get_command(argv[0]), visible); return ExitUserError; } ::extract_options o; // Verbosity settings. o.silent = (options.count("silent") != 0); o.quiet = o.silent || options.count("quiet"); logger::quiet = o.quiet; #ifdef DEBUG if(options.count("debug")) { logger::debug = true; } #endif o.warn_unused = (options.count("no-warn-unused") == 0); // Color / progress bar settings. color::is_enabled color_e; po::variables_map::const_iterator color_i = options.find("color"); if(color_i == options.end()) { color_e = o.silent ? color::disable : color::automatic; } else { color_e = color_i->second.as() ? color::enable : color::disable; } color::is_enabled progress_e; po::variables_map::const_iterator progress_i = options.find("progress"); if(progress_i == options.end()) { progress_e = o.silent ? color::disable : color::automatic; } else { progress_e = progress_i->second.as() ? color::enable : color::disable; } color::init(color_e, progress_e); // Help output. if(options.count("help") != 0) { print_help(get_command(argv[0]), visible); return ExitSuccess; } // License output if(options.count("license") != 0) { print_license(); return ExitSuccess; } // Main action. o.list = (options.count("list") != 0); o.extract = (options.count("extract") != 0); o.test = (options.count("test") != 0); o.list_languages = (options.count("list-languages") != 0); o.gog_game_id = (options.count("gog-game-id") != 0); bool explicit_action = o.list || o.test || o.extract || o.list_languages || o.gog_game_id; if(!explicit_action) { o.extract = true; } if(o.extract && o.test) { log_error << "Combining --extract and --test is not allowed!"; return ExitUserError; } if(!o.extract && !o.test) { progress::set_enabled(false); } if(!o.silent && (o.test || o.extract)) { o.list = true; } // Additional actions. o.filenames.set_expand(options.count("dump") == 0); o.filenames.set_lowercase(options.count("lowercase") != 0); // File timestamps { o.preserve_file_times = true, o.local_timestamps = false; po::variables_map::const_iterator i = options.find("timestamps"); if(i != options.end()) { std::string timezone = i->second.as(); if(boost::iequals(timezone, "none")) { o.preserve_file_times = false; } else if(!boost::iequals(timezone, "UTC")) { o.local_timestamps = true; if(!boost::iequals(timezone, "local")) { util::set_local_timezone(timezone); } } } } // List version. if(options.count("version") != 0) { print_version(o); if(!explicit_action) { return ExitSuccess; } } { o.collisions = OverwriteCollisions; po::variables_map::const_iterator i = options.find("collisions"); if(i != options.end()) { std::string collisions = i->second.as(); if(collisions == "overwrite") { o.collisions = OverwriteCollisions; } else if(collisions == "rename") { o.collisions = RenameCollisions; } else if(collisions == "rename-all") { o.collisions = RenameAllCollisions; } else if(collisions == "error") { o.collisions = ErrorOnCollisions; } else { log_error << "Unsupported --collisions value: " << collisions; return ExitUserError; } } } { po::variables_map::const_iterator i = options.find("default-language"); if(i != options.end()) { o.default_language = i->second.as(); } } o.extract_temp = (options.count("exclude-temp") == 0); { po::variables_map::const_iterator i = options.find("language"); if(i != options.end()) { o.language = i->second.as(); } o.language_only = (options.count("language-only") != 0); } { po::variables_map::const_iterator i = options.find("include"); if(i != options.end()) { o.include = i->second.as >(); } } if(options.count("setup-files") == 0) { if(!o.silent) { std::cout << get_command(argv[0]) << ": no input files specified\n"; std::cout << "Try the --help (-h) option for usage information.\n"; } return ExitSuccess; } { po::variables_map::const_iterator i = options.find("output-dir"); if(i != options.end()) { /* * We can't use fs::path directly with boost::program_options as fs::path's * operator>> expects paths to be quoted if they contain spaces, breaking * lexical casts. * Instead, do the conversion in the assignment operator. * See https://svn.boost.org/trac/boost/ticket/8535 */ o.output_dir = i->second.as(); try { if(!o.output_dir.empty() && !fs::exists(o.output_dir)) { fs::create_directory(o.output_dir); } } catch(...) { log_error << "Could not create output directory " << o.output_dir; return ExitDataError; } } } o.gog = (options.count("gog") != 0); const std::vector & files = options["setup-files"] .as< std::vector >(); bool suggest_bug_report = false; try { BOOST_FOREACH(const std::string & file, files) { process_file(file, o); } } catch(const std::ios_base::failure & e) { log_error << "Stream error while extracting files!\n" << " └─ error reason: " << e.what(); suggest_bug_report = true; } catch(const format_error & e) { log_error << e.what(); suggest_bug_report = true; } catch(const std::runtime_error & e) { log_error << e.what(); } catch(const setup::version_error &) { log_error << "Not a supported Inno Setup installer!"; } if(suggest_bug_report) { std::cerr << color::blue << "If you are sure the setup file is not corrupted," << " consider \nfiling a bug report at " << color::dim_cyan << innoextract_bugs << color::reset << '\n'; } if(!logger::quiet || logger::total_errors || logger::total_warnings) { progress::clear(); std::ostream & os = logger::quiet ? std::cerr : std::cout; os << color::green << "Done" << color::reset << std::dec; if(logger::total_errors || logger::total_warnings) { os << " with "; if(logger::total_errors) { os << color::red << logger::total_errors << ((logger::total_errors == 1) ? " error" : " errors") << color::reset; } if(logger::total_errors && logger::total_warnings) { os << " and "; } if(logger::total_warnings) { os << color::yellow << logger::total_warnings << ((logger::total_warnings == 1) ? " warning" : " warnings") << color::reset; } } os << '.' << std::endl; } return logger::total_errors == 0 ? ExitSuccess : ExitDataError; }