Example #1
0
int main(int argc, char *argv[]) {
	Settings ini;
	Game game;
	string gameStr;							// allow for autodetection override
	string bosslogFormat;
	fs::path sortfile;						//modlist/masterlist to sort plugins using.


	///////////////////////////////
	// Set up initial conditions
	///////////////////////////////

	LOG_INFO("BOSS starting...");

	LOG_INFO("Parsing Ini...");
	//Parse ini file if found. Can't just use BOOST's program options ini parser because of the CSS syntax and spaces.
	if (fs::exists(ini_path)) {
		try {
			ini.Load(ini_path);
		} catch (boss_error &e) {
			LOG_ERROR("Error: %s", e.getString().c_str());
			//Error will be added to log once format has been set.
		}
	} else {
		try {
			ini.Save(ini_path, AUTODETECT);
		} catch (boss_error &e) {
			ini.ErrorBuffer(ParsingError("Error: " + e.getString()));
		}
	}

	//Specify location of language dictionaries
	boost::locale::generator gen;
    gen.add_messages_path(l10n_path.string());
	gen.add_messages_domain("messages");

	//Set the locale to get encoding and language conversions working correctly.
	string localeId = "";
	if (gl_language == ENGLISH)
		localeId = "en.UTF-8";
	else if (gl_language == SPANISH)
		localeId = "es.UTF-8";
	else if (gl_language == GERMAN)
		localeId = "de.UTF-8";
	else if (gl_language == RUSSIAN)
		localeId = "ru.UTF-8";
	else if (gl_language == SIMPCHINESE)
		localeId = "zh.UTF-8";

	try {
		locale::global(gen(localeId));
		cout.imbue(locale());
	} catch(exception &e) {
		LOG_ERROR("could not implement translation: %s", e.what());
		cout << e.what() << endl;
	}
	locale global_loc = locale();
	locale loc(global_loc, new boost::filesystem::detail::utf8_codecvt_facet());
	boost::filesystem::path::imbue(loc);

	//////////////////////////////
	// Handle Command Line Args
	//////////////////////////////

	// declare the supported options
	po::options_description opts("Options");
    opts.add_options()
        ("help,h", translate("produces this help message").str().c_str())
        ("version,V", translate("prints the version banner").str().c_str())
        ("update,u", po::value(&gl_update)->zero_tokens(),
        translate("automatically update the local copy of the"
        " masterlist to the latest version"
        " available on the web before sorting").str().c_str())
        ("no-update,U", translate("inhibit the automatic masterlist updater").str().c_str())
        ("only-update,o", po::value(&gl_update_only)->zero_tokens(),
        translate("automatically update the local copy of the"
        " masterlist to the latest version"
        " available on the web but don't sort right"
        " now").str().c_str())
        ("silent,s", po::value(&gl_silent)->zero_tokens(),
        translate("don't launch a browser to show the HTML log"
        " at program completion").str().c_str())
        ("revert,r", po::value(&gl_revert)->implicit_value(1, ""),
        translate("revert to a previous load order.  this"
        " parameter optionally accepts values of 1 or"
        " 2, indicating how many undo steps to apply."
        "  if no option value is specified, it"
        " defaults to 1").str().c_str())
        ("verbose,v", po::value(&gl_debug_verbosity)->implicit_value(1, ""),
        translate("specify verbosity level (0-3) of the debugging output.  0 is the"
        " default, showing only WARN and ERROR messges."
        " 1 (INFO and above) is implied if this option"
        " is specified without an argument.  higher"
        " values increase the verbosity further").str().c_str())
        ("game,g", po::value(&gameStr),
        translate("override game autodetection.  valid values"
        " are: 'Oblivion', 'Nehrim', 'Fallout3',"
        " 'FalloutNV', and 'Skyrim'").str().c_str())
        ("crc-display,c", po::value(&gl_show_CRCs)->zero_tokens(),
        translate("show mod file CRCs, so that a file's CRC can be"
        " added to the masterlist in a conditional").str().c_str())
        ("format,f", po::value(&bosslogFormat),
        translate("select output format. valid values"
        " are: 'html', 'text'").str().c_str())
        ("trial-run,t", po::value(&gl_trial_run)->zero_tokens(),
        translate("run BOSS without actually making any changes to load order").str().c_str());

	// parse command line arguments
	po::variables_map vm;
	try{
		po::store(po::command_line_parser(argc, argv).options(opts).run(), vm);
		po::notify(vm);
	}catch (po::multiple_occurrences &){
		LOG_ERROR("cannot specify options multiple times; please use the '--help' option to see usage instructions");
		Fail();
	}catch (exception & e){
		LOG_ERROR("%s; please use the '--help' option to see usage instructions", e.what());
		Fail();
	}

	// set alternative output stream for logger and whether to track log statement origins
    if (gl_debug_verbosity > 0)
	    g_logger.setStream(debug_log_path.string().c_str());
	if (gl_debug_verbosity < 0) {
		LOG_ERROR("invalid option for 'verbose' parameter: %d", gl_debug_verbosity);
		Fail();
	}
	g_logger.setVerbosity(static_cast<LogVerbosity>(LV_WARN + gl_debug_verbosity));  // it's ok if this number is too high.  setVerbosity will handle it

	if (vm.count("help")) {
		ShowUsage(opts);
		exit(0);
	}
	if (vm.count("version")) {
		ShowVersion();
		exit(0);
	}
	if (vm.count("no-update")) {
		gl_update = false;
	}
	if ((vm.count("update")) && (vm.count("no-update"))) {
		LOG_ERROR("invalid options: --update,-u and --no-update,-U cannot both be given.");
		Fail();
	}
	if (vm.count("revert") && (gl_revert < 1 || gl_revert > 2)) {
		LOG_ERROR("invalid option for 'revert' parameter: %d", gl_revert);
		Fail();
	}
	if (vm.count("game")) {
		// sanity check and parse argument
		if      (boost::iequals("Oblivion",   gameStr))
			gl_game = OBLIVION;
		else if (boost::iequals("Fallout3",   gameStr))
			gl_game = FALLOUT3;
		else if (boost::iequals("Nehrim",     gameStr))
			gl_game = NEHRIM;
		else if (boost::iequals("FalloutNV", gameStr))
			gl_game = FALLOUTNV;
		else if (boost::iequals("Skyrim", gameStr))
			gl_game = SKYRIM;
		else {
			LOG_ERROR("invalid option for 'game' parameter: '%s'", gameStr.c_str());
			Fail();
		}
		LOG_DEBUG("game ini setting overridden with: '%s' (%d)", gameStr.c_str(), gl_game);
	}
	if (vm.count("format")) {
		// sanity check and parse argument
		string bosslogFormat = vm["format"].as<string>();
		if (bosslogFormat == "html")
			gl_log_format = HTML;
		else if (bosslogFormat == "text")
			gl_log_format = PLAINTEXT;
		else {
			LOG_ERROR("invalid option for 'format' parameter: '%s'", bosslogFormat.c_str());
			Fail();
		}
		LOG_DEBUG("BOSSlog format set to: '%s'", bosslogFormat.c_str());
	}


	/////////////////////////////////////////
	// Check for critical error conditions
	/////////////////////////////////////////

	//Game checks.
	LOG_DEBUG("Detecting game...");
	try {
		gl_last_game = AUTODETECT;  //Clear this setting in case the GUI was run.
		vector<uint32_t> detected, undetected;
		uint32_t detectedGame = DetectGame(detected, undetected);
		if (detectedGame == AUTODETECT) {
			//Now check what games were found.
			if (detected.empty())
				throw boss_error(BOSS_ERROR_NO_GAME_DETECTED);
			else if (detected.size() == 1)
				detectedGame = detected.front();
			else {
				size_t ans;
				//Ask user to choose game.
				cout << endl << translate("Please pick which game to run BOSS for:") << endl;
				for (size_t i=0; i < detected.size(); i++)
					cout << i << " : " << Game(detected[i], "", true).Name() << endl;

				cin >> ans;
				if (ans < 0 || ans >= detected.size()) {
					cout << translate("Invalid selection.") << endl;
					throw boss_error(BOSS_ERROR_NO_GAME_DETECTED);
				}
				detectedGame = detected[ans];
			}
		}
		game = Game(detectedGame);
		game.CreateBOSSGameFolder();
		LOG_INFO("Game detected: %s", game.Name().c_str());
	} catch (boss_error &e) {
		LOG_ERROR("Critical Error: %s", e.getString().c_str());
		game.bosslog.SetFormat(gl_log_format);
		game.bosslog.criticalError << LIST_ITEM_CLASS_ERROR << translate("Critical Error: ") << e.getString() << LINE_BREAK
			<< translate("Check the Troubleshooting section of the ReadMe for more information and possible solutions.") << LINE_BREAK
			<< translate("Utility will end now.");
		try {
			game.bosslog.Save(game.Log(gl_log_format), true);
		} catch (boss_error &e) {
			LOG_ERROR("Critical Error: %s", e.getString().c_str());
		}
		if ( !gl_silent )
			Launch(game.Log(gl_log_format).string());	//Displays the BOSSlog.txt.
		exit (1); //fail in screaming heap.
	}
	game.bosslog.SetFormat(gl_log_format);
	game.bosslog.parsingErrors.push_back(ini.ErrorBuffer());


	/////////////////////////////////////////////////////////
	// Update masterlist
	/////////////////////////////////////////////////////////

	if (gl_revert < 1 && (gl_update || gl_update_only)) {
		cout << endl << translate("Updating to the latest masterlist from the online repository...") << endl;
		LOG_DEBUG("Updating masterlist...");
		try {
            string revision = UpdateMasterlist(game, progress, NULL);
            string message = (boost::format(translate("Masterlist updated; at revision: %1%.")) % revision).str();
			game.bosslog.updaterOutput << LIST_ITEM_CLASS_SUCCESS << message;
			cout << endl << message << endl;
		} catch (boss_error &e) {
            game.bosslog.updaterOutput << LIST_ITEM_CLASS_ERROR << translate("Error: masterlist update failed.") << LINE_BREAK
                << (boost::format(translate("Details: %1%")) % e.getString()).str() << LINE_BREAK;
			LOG_ERROR("Error: masterlist update failed. Details: %s", e.getString().c_str());
		}
    }
    else {
        string revision = GetMasterlistVersion(game);
        string message = (boost::format(translate("Masterlist updating disabled; at revision: %1%.")) % revision).str();
        game.bosslog.updaterOutput << LIST_ITEM_CLASS_SUCCESS << message;
    }

	//If true, exit BOSS now. Flush earlyBOSSlogBuffer to the bosslog and exit.
	if (gl_update_only) {
		try {
			game.bosslog.Save(game.Log(gl_log_format), true);
		} catch (boss_error &e) {
			LOG_ERROR("Critical Error: %s", e.getString().c_str());
		}
		if ( !gl_silent )
			Launch(game.Log(gl_log_format).string());	//Displays the BOSSlog.
		return (0);
	}


	///////////////////////////////////
	// Resume Error Condition Checks
	///////////////////////////////////

	cout << endl << translate("BOSS working...") << endl;

	//Build and save modlist.
	try {
		game.modlist.Load(game, game.DataFolder());
		if (gl_revert < 1)
			game.modlist.Save(game.Modlist(), game.OldModlist());
	} catch (boss_error &e) {
		LOG_ERROR("Failed to load/save modlist, error was: %s", e.getString().c_str());
		game.bosslog.criticalError << LIST_ITEM_CLASS_ERROR << (boost::format(translate("Critical Error: %1%")) % e.getString()).str() << LINE_BREAK
			<< translate("Check the Troubleshooting section of the ReadMe for more information and possible solutions.") << LINE_BREAK
			<< translate("Utility will end now.");
		try {
			game.bosslog.Save(game.Log(gl_log_format), true);
		} catch (boss_error &e) {
			LOG_ERROR("Critical Error: %s", e.getString().c_str());
		}
		if ( !gl_silent )
			Launch(game.Log(gl_log_format).string());	//Displays the BOSSlog.txt.
		exit (1); //fail in screaming heap.
	}


	/////////////////////////////////
	// Parse Master- and Userlists
	/////////////////////////////////
	//masterlist parse errors are critical, ini and userlist parse errors are not.

	//Set masterlist path to be used.
	if (gl_revert == 1)
		sortfile = game.Modlist();
	else if (gl_revert == 2)
		sortfile = game.OldModlist();
	else
		sortfile = game.Masterlist();
	LOG_INFO("Using sorting file: %s", sortfile.string().c_str());

	//Parse masterlist/modlist backup into data structure.
	try {
		LOG_INFO("Starting to parse sorting file: %s", sortfile.string().c_str());
		game.masterlist.Load(game, sortfile);
		LOG_INFO("Starting to parse conditionals from sorting file: %s", sortfile.string().c_str());
		game.masterlist.EvalConditions(game);
		game.masterlist.EvalRegex(game);
		game.bosslog.globalMessages = game.masterlist.GlobalMessageBuffer();
		game.bosslog.parsingErrors.push_back(game.masterlist.ErrorBuffer());
	} catch (boss_error &e) {
		LOG_ERROR("Critical Error: %s", e.getString().c_str());
        if (e.getCode() == BOSS_ERROR_FILE_PARSE_FAIL)
			game.bosslog.criticalError << game.masterlist.ErrorBuffer();
		else if (e.getCode() == BOSS_ERROR_CONDITION_EVAL_FAIL)
			game.bosslog.criticalError << LIST_ITEM_CLASS_ERROR << e.getString();
		else
			game.bosslog.criticalError << LIST_ITEM_CLASS_ERROR << (boost::format(translate("Critical Error: %1%")) % e.getString()).str() << LINE_BREAK
				<< translate("Check the Troubleshooting section of the ReadMe for more information and possible solutions.") << LINE_BREAK
				<< translate("Utility will end now.");
		try {
			game.bosslog.Save(game.Log(gl_log_format), true);
		} catch (boss_error &e) {
			LOG_ERROR("Critical Error: %s", e.getString().c_str());
		}
		if ( !gl_silent )
                Launch(game.Log(gl_log_format).string());  //Displays the BOSSlog.txt.
        exit (1); //fail in screaming heap.
	}

	LOG_INFO("Starting to parse userlist.");
	try {
		game.userlist.Load(game, game.Userlist());
		vector<ParsingError> errs = game.userlist.ErrorBuffer();
		game.bosslog.parsingErrors.insert(game.bosslog.parsingErrors.end(), errs.begin(), errs.end());
	} catch (boss_error &e) {
		vector<ParsingError> errs = game.userlist.ErrorBuffer();
		game.bosslog.parsingErrors.insert(game.bosslog.parsingErrors.end(), errs.begin(), errs.end());
		game.userlist.Clear();  //If userlist has parsing errors, empty it so no rules are applied.
		LOG_ERROR("Error: %s", e.getString().c_str());
	}

	//////////////////////////////////
	// Perform sorting functionality
	//////////////////////////////////

	try {
		game.ApplyMasterlist();
		LOG_INFO("masterlist now filled with ordered mods and modlist filled with unknowns.");
		game.ApplyUserlist();
		LOG_INFO("userlist sorting process finished.");
		game.ScanSEPlugins();
		game.SortPlugins();
		game.bosslog.Save(game.Log(gl_log_format), true);
	} catch (boss_error &e) {
		LOG_ERROR("Critical Error: %s", e.getString().c_str());
		game.bosslog.criticalError << LIST_ITEM_CLASS_ERROR << (boost::format(translate("Critical Error: %1%")) % e.getString()).str() << LINE_BREAK
			<< translate("Check the Troubleshooting section of the ReadMe for more information and possible solutions.") << LINE_BREAK
			<< translate("Utility will end now.");
		try {
			game.bosslog.Save(game.Log(gl_log_format), true);
		} catch (boss_error &e) {
			LOG_ERROR("Critical Error: %s", e.getString().c_str());
		}
		if ( !gl_silent )
                Launch(game.Log(gl_log_format).string());  //Displays the BOSSlog.txt.
        exit (1); //fail in screaming heap.
	}

	LOG_INFO("Launching boss log in browser.");
	if ( !gl_silent )
		Launch(game.Log(gl_log_format).string());	//Displays the BOSSlog.txt.
	LOG_INFO("BOSS finished.");
	return (0);
}