/***************************************************************************\ | Project: AE Installer | | By: Gumby & Iritscen | | File: Installer.cpp | | Function: Contains the real meat of the installation process. Mm, beefy. | | Created: 24/05/2009 19:39:00 | \***************************************************************************/ // TODO: Load credits from text resource file // TODO: Clear mod info fields when mod is de-selected //#define DEBUG #ifdef WIN32 //#include #define popen _popen #endif #include "boost/date_time/gregorian/gregorian.hpp" #include "boost/date_time/date_parsing.hpp" #include "boost/date_time/posix_time/posix_time.hpp" #include #include "installer.h" #include "aeinstallerapp.h" using namespace boost::gregorian; using namespace boost::posix_time; // externs declared in installer.h string strInstallCfg = "../GameDataFolder/Add.cfg"; string strEUFN = "Edition"; // GetUpdateStatus() may set this to "Edition-patch" later, but this is the assumed name of the new Edition folder in Updates/ extern MainWindow* TheWindow; int globalizeData(void) { busy = 1; using boost::lexical_cast; using boost::bad_lexical_cast; using namespace boost::gregorian; using namespace boost::posix_time; ptime start_time(second_clock::local_time()); setStatusArea("Globalizing!"); int err = 0; int parts_done = 0; remove("Globalize.log"); ofstream logfile("Globalize.log"); logfile << "Globalization started " << to_simple_string(start_time) << endl; try { // the levels Oni has...probably should have made a string array. Oops. char levels_cstr[15][3] = {"0", "1", "2", "3", "4", "6", "8", "9", "10", "11", "12", "13", "14", "18", "19"}; vector levels; for (int f = 0; f < 15; f++) { levels.push_back(levels_cstr[f]); } path Characters = "../GameDataFolder/level0_Characters"; path Particles = "../GameDataFolder/level0_Particles"; path Archive = "../GameDataFolder/Archive"; path Textures = "../GameDataFolder/level0_Textures"; path Sounds = "../GameDataFolder/level0_Sounds"; path Animations = "../GameDataFolder/level0_Animations"; path TRAC = Animations / "level0_TRAC"; path TRAM = Animations / "level0_TRAM"; vector GDFPaths; GDFPaths.push_back(Particles); GDFPaths.push_back(Textures); GDFPaths.push_back(Sounds); GDFPaths.push_back(TRAC); GDFPaths.push_back(TRAM); path VanillaCharacters = "VanillaDats/level0_Final/level0_Characters/level0_Characters.oni"; path VanillaParticles = "VanillaDats/level0_Final/level0_Particles/level0_Particles.oni"; path VanillaTextures = "VanillaDats/level0_Final/level0_Textures/level0_Textures.oni"; path VanillaSounds = "VanillaDats/level0_Final/level0_Sounds/level0_Sounds.oni"; path VanillaAnimations = "VanillaDats/level0_Final/level0_Animations/level0_Animations.oni"; path VanillaTRAC = "VanillaDats/level0_Final/level0_Animations/level0_TRAC.oni"; path VanillaTRAM = "VanillaDats/level0_Final/level0_Animations/level0_TRAM.oni"; vector VanillaPaths; VanillaPaths.push_back(VanillaParticles); VanillaPaths.push_back(VanillaTextures); VanillaPaths.push_back(VanillaSounds); VanillaPaths.push_back(VanillaTRAC); VanillaPaths.push_back(VanillaTRAM); setStatusArea("Removing old GameDataFolder...\n"); logfile << "Removing old GameDataFolder...\n"; remove_all( "../GameDataFolder/" ); setStatusArea("Creating needed directories..."); logfile << "Creating needed directories...\n"; create_directory( "../GameDataFolder/" ); create_directory( "packages" ); if (exists("VanillaDats")) remove_all("VanillaDats"); create_directory( "VanillaDats" ); create_directory( "VanillaDats/level0_Final/" ); create_directory( Characters ); create_directory( Particles ); create_directory( Archive ); create_directory( Textures ); create_directory( Sounds ); create_directory( Animations ); create_directory( TRAC ); create_directory( TRAM ); int num_levels = 0; for(int i = 1; i < 15; i++) { if (exists("../../GameDataFolder/level" + levels[i] + "_Final.dat")) { num_levels++; } } logfile << "Exporting and moving...\n\n"; int total_steps = 8 + 2 * num_levels; for(int i = 0; i < 15; i++) { if (exists("../../GameDataFolder/level" + levels[i] + "_Final.dat")) { logfile << "level" << levels[i] << "_Final\n"; logfile << "\tExporting level" << levels[i] << "_Final.dat\n"; setStatusArea("Step " + lexical_cast(parts_done + 1) + "/" + lexical_cast(total_steps) + " exporting level" + levels[i]+"_Final.dat"); create_directory( "../GameDataFolder/level" + levels[i] + "_Final" ); system((strOniSplit + " -export ../GameDataFolder/level" + levels[i] + "_Final ../../GameDataFolder/level" + levels[i] + "_Final.dat").c_str()); create_directory( "VanillaDats/level" + levels[i] + "_Final" ); create_directory( "VanillaDats/level" + levels[i] + "_Final/level" + levels[i] + "_Final" ); //Moves the AKEV and other files into a safe directory so that level specific textures are not globalized... if ( strcmp(levels[i].c_str(), "0") ){ create_directory( "../GameDataFolder/level" + levels[i] + "_Final/AKEV" ); system((strOniSplit + " -move:overwrite ../GameDataFolder/level" + levels[i] + "_Final/AKEV ../GameDataFolder/level" + levels[i] + "_Final/AKEV*.oni").c_str()); } directory_iterator end_iter; for ( directory_iterator dir_itr( "../GameDataFolder/level" + levels[i] + "_Final" ); dir_itr != end_iter; ++dir_itr ) { if ( is_regular_file( dir_itr->status() ) ) { if ( dir_itr->path().filename().substr(0,8) == "TXMPfail" || dir_itr->path().filename().substr(0,9) == "TXMPlevel" || ( dir_itr->path().filename().substr(0,4) == "TXMP" && dir_itr->path().filename().find("intro")!=string::npos) || dir_itr->path().filename().substr(0,4) == "TXMB" || dir_itr->path().filename() == "M3GMpowerup_lsi.oni" || dir_itr->path().filename() == "TXMPlsi_icon.oni" || ( dir_itr->path().filename().substr(0,4) == "TXMB" && dir_itr->path().filename().find("splash_screen.oni")!=string::npos) ) { cout <path().filename() << "\n"; create_directory( dir_itr->path().parent_path() / "NoGlobal"); if(!exists( dir_itr->path().parent_path() / "NoGlobal" / dir_itr->filename())) rename(dir_itr->path(), dir_itr->path().parent_path() / "NoGlobal" / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "TRAC" ) { cout <path().filename() << "\n"; if(!exists( TRAC / dir_itr->filename())) rename(dir_itr->path(), TRAC / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "TRAM") { cout <path().filename() << "\n"; if(!exists( TRAM / dir_itr->filename())) rename(dir_itr->path(), TRAM / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "ONSK" || dir_itr->path().filename().substr(0,4) == "TXMP") { cout <path().filename() << "\n";\ create_directory( dir_itr->path().parent_path() / "TexFix"); if(!exists( Textures / dir_itr->filename())) rename(dir_itr->path(), Textures / dir_itr->filename()); } else if (dir_itr->path().filename().substr(0,4) == "ONCC" || dir_itr->path().filename().substr(0,4) == "TRBS" || dir_itr->path().filename().substr(0,4) == "ONCV" || dir_itr->path().filename().substr(0,4) == "ONVL" || dir_itr->path().filename().substr(0,4) == "TRMA" || dir_itr->path().filename().substr(0,4) == "TRSC" || dir_itr->path().filename().substr(0,4) == "TRAS") { cout <path().filename() << "\n"; if(!exists( Characters / dir_itr->filename())) rename(dir_itr->path(), Characters / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "OSBD" || dir_itr->path().filename().substr(0,4) == "SNDD") { cout << dir_itr->path().filename() << "\n"; if(!exists( Sounds / dir_itr->filename())) rename(dir_itr->path(), Sounds / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,5) == "BINA3" || dir_itr->path().filename().substr(0,10) == "M3GMdebris" || dir_itr->path().filename() == "M3GMtoxic_bubble.oni" || dir_itr->path().filename().substr(0,8) == "M3GMelec" || dir_itr->path().filename().substr(0,7) == "M3GMrat" || dir_itr->path().filename().substr(0,7) == "M3GMjet" || dir_itr->path().filename().substr(0,9) == "M3GMbomb_" || dir_itr->path().filename() == "M3GMbarab_swave.oni" || dir_itr->path().filename() == "M3GMbloodyfoot.oni" ){ cout <path().filename() << "\n"; if(!exists( Particles / dir_itr->filename())) rename(dir_itr->path(), Particles / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "AGDB" || dir_itr->path().filename().substr(0,4) == "TRCM") { cout <path().filename() << "\n"; if(!exists( Archive / dir_itr->filename())) rename(dir_itr->path(), Archive / dir_itr->filename()); else remove(dir_itr->path()); } else if (dir_itr->path().filename().substr(0,4) == "ONWC") { //fix for buggy ONWC overriding cout <path().filename() << "\n"; if(!exists( "VanillaDats/level0_Final/level0_Final/" + dir_itr->filename())) rename(dir_itr->path(), "VanillaDats/level0_Final/level0_Final/" + dir_itr->filename()); else remove(dir_itr->path()); } if (exists(dir_itr->path())) { } else { //logfile << "\tMoved file: " << dir_itr->path().filename() << "\n"; } } } logfile << "\tCleaning up TXMPs...\n"; system( (strOniSplit + " -move:delete " + Textures.string() + " ../GameDataFolder/level" + levels[i] + "_Final/TXMP*.oni").c_str()); if ( strcmp(levels[i].c_str(), "0") ){ system((strOniSplit + " -move:overwrite ../GameDataFolder/level" + levels[i] + "_Final ../GameDataFolder/level" + levels[i] + "_Final/AKEV/AKEV*.oni").c_str()); remove( "../GameDataFolder/level" + levels[i] + "_Final/AKEV" ); } parts_done++; setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) )); } } logfile << "Reimporting levels\n"; for (int i = 0; i < 15; i++) { logfile << "\tReimporting level" << levels[i] << "_Final.oni\n"; setStatusArea("Step " + lexical_cast(parts_done + 1) + "/" + lexical_cast(total_steps) + " reimporting level" + levels[i] + "_Final.oni"); logfile << (strOniSplit + " " + strImportOption + " ../GameDataFolder/level" + levels[i] + "_Final VanillaDats/level" + levels[i] + "_Final/level" + levels[i] + "_Final/level" + levels[i] + "_Final.oni >> Globalize.log").c_str() << '\n'; string sys_str = (strOniSplit + " " + strImportOption + " ../GameDataFolder/level" + levels[i] + "_Final VanillaDats/level" + levels[i] + "_Final/level" + levels[i] + "_Final/level" + levels[i] + "_Final.oni"); system(sys_str.c_str() ); setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) )); parts_done++; } create_directory( VanillaParticles.parent_path() ); create_directory( VanillaTextures.parent_path() ); create_directory( VanillaSounds.parent_path() ); create_directory( VanillaAnimations.remove_filename() ); for(unsigned int j = 0; j < GDFPaths.size(); j++) { logfile << "\tReimporting " << GDFPaths[j].filename() << ".oni\n"; setStatusArea("Step " + lexical_cast(parts_done + 1) + "/" + lexical_cast(total_steps) + ": reimporting " + GDFPaths[j].filename() ); system((strOniSplit + " " + strImportOption + " " + GDFPaths[j].string() + " " + VanillaPaths[j].string()).c_str()); parts_done++; setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) )); } logfile << "\nMoving level0_Characters\n"; setStatusArea("Step " + lexical_cast(parts_done + 1) + "/" + lexical_cast(total_steps) + ": moving level0_Characters" ); copy((path)"../GameDataFolder/level0_Characters", (path)("VanillaDats/level0_Final")); GDFPaths.push_back( Characters ); //concactates level0.... for(int i = 0; i < GDFPaths.size(); i++) { directory_iterator end_iter; for ( directory_iterator dir_itr( GDFPaths[i] ); dir_itr != end_iter; ++dir_itr ) { try { rename(dir_itr->path(), "../GameDataFolder/level0_Final/" + dir_itr->path().filename() ); } catch(exception &ex) { } } } //?: syntax is fun. //condition ? value_if_true : value_if_false (is_empty(Characters) ? remove( Characters ) : 1); (is_empty(Particles) ? remove( Particles ) : 1); (is_empty(Textures) ? remove( Textures ) : 1); (is_empty(Sounds) ? remove( Sounds ) : 1); (is_empty(TRAC) ? remove( TRAC ) : 1); (is_empty(TRAM) ? remove( TRAM ) : 1); (is_empty(Animations) ? remove( Animations ) : 1); create_directory((path)"../GameDataFolder/IGMD"); copy((path)"packages/VanillaBSL/IGMD", (path)"../GameDataFolder"); setProgressBar( 1000 ); if(exists("../../persist.dat") && !exists("../persist.dat")) copy("../../persist.dat",".."); if(exists("../../key_config.txt")&& !exists("../key_config.txt")) copy("../../key_config.txt",".."); #ifndef WIN32 /* On Mac only, set the current GDF to the AE GDF by writing to Oni's global preferences file (thankfully a standard OS X ".plist" XML file). Tests for presence of prefs with [ -f ] before doing anything so it doesn't create a partial prefs file -- just in case user has never run Oni before :-p */ string fullAEpath = escapePath(system_complete(".").parent_path().parent_path().string()); // get full path for Edition/ (Oni wants folder that *contains* the GDF) //bad Iritscen, bad! fixed buffers can cause crashes. /*char prefsCommand[300] = "[ -f ~/Library/Preferences/com.godgames.oni.plist ] && defaults write com.godgames.oni RetailInstallationPath -string '"; strcat(prefsCommand, fullAEpath.c_str()); strcat(prefsCommand, "'"); // path string is enclosed in single quotes to avoid the need to escape UNIX-unfriendly characters */ string prefsCommand = "[ -f ~/Library/Preferences/com.godgames.oni.plist ] && defaults write com.godgames.oni RetailInstallationPath -string '" + fullAEpath + "'"; system(prefsCommand.c_str()); #endif setStatusArea((string)"Done! Now select your mod packages and click install."); } catch (exception & ex) { setStatusArea("Warning, handled exception: " + (string)ex.what()); } ptime end_time(second_clock::local_time()); time_period total_time (start_time, end_time); logfile << "\n\nGlobalization ended " << to_simple_string(end_time) << "\nThe process took " << total_time.length(); logfile.close(); busy = 0; return err; } vector getPackages(string packageDir) { vector packages; ModPackage package; packages.reserve(256); fstream file; string filename = "\0"; string MODINFO_CFG = "Mod_Info.cfg"; try { for (directory_iterator dir_itr(packageDir), end_itr; dir_itr != end_itr; ++dir_itr) { file.open((dir_itr->path().string() + "/" + MODINFO_CFG).c_str()); if (!file.fail()) { package = fileToModPackage(file, dir_itr->path().filename()); if (package.installerVersion.compare(INSTALLER_VERSION) < 1) // if mod requires newer version of the Installer, we won't add it to the list { #ifdef WIN32 if (!package.platform.compare("Windows") || !package.platform.compare("Both")) // don't show package if it's not for the right OS #else if (!package.platform.compare("Macintosh") || !package.platform.compare("Both")) #endif packages.push_back(package); } } file.close(); file.clear(); } sort(packages.begin(), packages.end()); } catch (const std::exception & ex) { cout << "Warning, something odd happened!\n"; } return packages; } ModPackage fileToModPackage(fstream &file, string modName) { ModPackage package; string line; const string AEInstallVersion = "AEInstallVersion"; // used for comparing to the current token... const string NameOfMod = "NameOfMod"; const string ARROW = "->"; const string ModString = "ModString"; const string ModVersion = "ModVersion"; const string Platform = "Platform"; const string HasOnis = "HasOnis"; const string HasDeltas = "HasDeltas"; const string HasBSL = "HasBSL"; const string HasDats = "HasDats"; const string IsEngine = "IsEngine"; const string Readme = "Readme"; const string GlobalNeeded = "GlobalNeeded"; const string Category = "Category"; const string Creator = "Creator"; package.modStringName = modName; while (!file.eof()) { getline(file,line); vector tokens; vector::iterator iter; tokenize(line, tokens); if (tokens.capacity() >= 3) { iter = tokens.begin(); if (!AEInstallVersion.compare(*iter)) { iter++; iter++; package.installerVersion = *iter; } else if (!NameOfMod.compare(*iter)) { for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++) // iterates through the words, ends if it reaches the end of the line or a "//" comment { if (ARROW.compare(*iter) && NameOfMod.compare(*iter)) // ignores "->" and "NameOfMod" package.name += *iter + " "; } } else if (!ModString.compare(*iter)) { iter++; iter++; //package.modStringName = *iter; iter++; package.modStringVersion = atof((*iter).c_str()); } else if (!ModString.compare(*iter)) { iter++; iter++; package.modStringVersion = atof((*iter).c_str()); } else if (!Platform.compare(*iter)) { iter++; iter++; package.platform = *iter; } else if (!HasOnis.compare(*iter)) { iter++; iter++; if (boost::iequals(*iter, "Yes")) package.hasOnis = 1; } else if (!HasBSL.compare(*iter)) { iter++; iter++; if (boost::iequals(*iter, "Yes")) package.hasBSL = true; else if (boost::iequals(*iter, "Addon")) package.hasAddon = true; } else if (!HasDeltas.compare(*iter)) { iter++; iter++; if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.hasDeltas = 1; } else if (!HasDats.compare(*iter)) { iter++; iter++; if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.hasDats = 1; } else if (!IsEngine.compare(*iter)) { iter++; iter++; if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.isEngine = 1; } else if (!GlobalNeeded.compare(*iter)) { iter++; iter++; if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.globalNeeded = 1; else if (toupper((*iter)[0]) == 'N' && toupper((*iter)[1]) == 'O') package.globalNeeded = 1; // only place where checking for "No" is important atm } else if (!Category.compare(*iter)) { for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++) { if (ARROW.compare(*iter) && Category.compare(*iter)) // ignores "->" and "Category" package.category += *iter + " "; } } else if (!Creator.compare(*iter)) { for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++) { if (ARROW.compare(*iter) && Creator.compare(*iter)) // ignores "->" and "Creator" package.creator += *iter + " "; } } else if (!Readme.compare(*iter)) { for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++) { if (ARROW.compare(*iter) && Readme.compare(*iter)) // ignores "->" and "Readme" { if (!(*iter).compare("\\n")) package.readme += '\n'; else package.readme += *iter + " "; } } } } } return package; } void recompileAll(vector installedMods) {try { busy = 1; using namespace boost::gregorian; using namespace boost::posix_time; using boost::lexical_cast; using boost::bad_lexical_cast; path vanilla_dir = "./VanillaDats/"; string importCommand = ""; int numberOfDats = 0; int j = 1; string datString; setStatusArea("Importing levels..."); std::stringstream out; ptime start_time(second_clock::local_time()); clearOldDats(); if(exists("Install.log")) remove("Install.log"); ofstream logfile("Install.log"); logfile << "Mod Installation started " << to_simple_string(start_time) << endl; logfile.close(); if(splitInstances == true) { recursive_directory_iterator end_iter; for ( recursive_directory_iterator dir_itr( vanilla_dir ); dir_itr != end_iter; ++dir_itr ) { try{ if ( is_directory( dir_itr->status() ) && dir_itr.level() == 1) { numberOfDats++; } } catch(exception & ex) { remove("Install.log"); ofstream logfile("Install.log"); logfile << "Warning, exception " << ex.what() << "!"; setStatusArea("Warning, exception " + (string)ex.what() + "!"); logfile.close(); } } try { out << numberOfDats; datString = out.str(); for ( recursive_directory_iterator dir_itr( vanilla_dir ); dir_itr != end_iter; ++dir_itr ) { try { if ( is_directory( dir_itr->status() ) && dir_itr.level() == 1) { importCommand = strOniSplit + " " + strImportOption + " " + dir_itr->path().parent_path().string() + '/' + dir_itr->path().filename(); for (unsigned int i = 0; i < installedMods.size(); ++i) { if (exists("packages/" + installedMods[i] + "/oni/" + dir_itr->path().parent_path().filename() + '/' + dir_itr->path().filename() )) importCommand += " packages/" + installedMods[i] + "/oni/" + dir_itr->path().parent_path().filename() + '/' + dir_itr->path().filename(); } importCommand += " ../GameDataFolder/" + dir_itr->path().filename() + ".dat >> Install.log"; setProgressBar( (int)(1000 * (float)(j-1) / (float)numberOfDats) ); //100% * dat we're on / total dats setStatusArea("Step " + lexical_cast(j) + '/' + lexical_cast(numberOfDats)+ ": Importing " + dir_itr->path().filename() + " "); system(importCommand.c_str()); j++; } } catch ( const std::exception & ex ) { remove("Install.log"); ofstream logfile("Install.log"); logfile << "Warning, exception " << ex.what() << "!"; setStatusArea("Warning, exception " + (string)ex.what() + "!"); logfile.close(); } } } catch( const std::exception & ex ) { remove("Install.log"); ofstream logfile("Install.log"); logfile << "Warning, exception " << ex.what() << "!"; setStatusArea("Warning, exception " + (string)ex.what() + "!"); logfile.close(); } } else if(splitInstances == false){ directory_iterator end_iter; for ( directory_iterator dir_itr( vanilla_dir ); dir_itr != end_iter; ++dir_itr ) { if ( is_directory( dir_itr->status() ) ) { numberOfDats++; } } out << numberOfDats; datString = out.str(); for ( directory_iterator dir_itr( vanilla_dir ); dir_itr != end_iter; ++dir_itr ) { try { if ( is_directory( dir_itr->status() ) ) { importCommand = strOniSplit + " " + strImportOption + " " + vanilla_dir.string() + dir_itr->path().filename() + " "; for (unsigned int i = 0; i < installedMods.size(); ++i) { if (exists("packages/" + installedMods[i] + "/oni/" + dir_itr->path().filename() )) importCommand += " packages/" + installedMods[i] + "/oni/" + dir_itr->path().filename(); } importCommand += " ../GameDataFolder/" + dir_itr->path().filename() + ".dat >> Install.log"; setProgressBar( (int)(1000 * (float)(j-1) / (float)numberOfDats) ); //100% * dat we're on / total dats setStatusArea("Step " + lexical_cast(j) + '/' + lexical_cast(numberOfDats)+ ": Importing " + dir_itr->path().filename() + " "); system(importCommand.c_str()); j++; } } catch ( const std::exception & ex ) { remove("Install.log"); ofstream logfile("Install.log"); logfile << "Warning, exception " << ex.what() << "!"; setStatusArea("Warning, exception " + (string)ex.what() + "!"); logfile.close(); } } } vector BSLfolders; vector skippedfolders; ofstream BSLlog("BSL.log"); for ( directory_iterator dir_itr( "../GameDataFolder/IGMD/" ), end_itr; dir_itr != end_itr; ++dir_itr ) { if( exists(dir_itr->path().string() + "/ignore.txt") ){ BSLfolders.push_back(dir_itr->path().filename()); skippedfolders.push_back(dir_itr->path().filename()); } } for (int i = installedMods.size() - 1; i >= 0; i--) { //Iterates through the installed mods (backwards :P) for (unsigned int j = 0; j < globalPackages.size(); ++j) { //looking in the global packages if (globalPackages[j].modStringName == installedMods[i]) { //for a mod that has BSL in it if(!(globalPackages[j].hasAddon || globalPackages[j].hasBSL)) break; //skip non-BSL if( exists( "packages/" + globalPackages[j].modStringName + "/BSL/" ) ) { copyBSL("packages/" + globalPackages[j].modStringName + "/BSL", BSLfolders, globalPackages[j] ); BSLlog << "Copied " << globalPackages[j].modStringName << "!\n"; } } } } ModPackage emptyPackage; emptyPackage.modStringName = "VanillaBSL"; emptyPackage.hasBSL = 1; copyBSL("packages/VanillaBSL/IGMD", BSLfolders, emptyPackage); BSLlog.close(); logfile << "Writing config file"; writeInstalledMods(installedMods); setProgressBar(1000); string finallyDone = "Done! You can now play Oni."; setStatusArea(finallyDone); ptime end_time(second_clock::local_time()); time_period total_time (start_time, end_time); ofstream logfile2("Install.log", ios::app | ios::ate); string outstring = (string)"\n\nGlobalization ended " + to_simple_string(end_time) + "\nThe process took ";// + (string)total_time.length(); logfile2 << "\nInstallation ended " << to_simple_string(end_time) << "\nThe process took " << total_time.length(); logfile2.close(); Sleep(1000); setProgressBar(0); } catch(exception & ex) { remove("Install.log"); //why did we do this? :| ofstream logfile("Install.log"); logfile << "Warning, exception " << ex.what() << "!"; setStatusArea("Warning, exception " + (string)ex.what() + "!"); logfile.close(); } busy = 0; } void copyBSL(string copypath, vector& BSLfolders, ModPackage pkg) { ofstream BSLlog("BSL.log", ios::app ); try { for ( directory_iterator dir_itr( copypath ), end_itr; dir_itr != end_itr; ++dir_itr ) { if ( is_directory( dir_itr->path() ) && dir_itr->path().string() != ".svn" ) { BSLlog << "Testing " << dir_itr->path().string() << " HasBSL: " << pkg.hasBSL << " HasAddon: " << pkg.hasAddon << "\n"; int skip_folder = 0; for(unsigned int k = 0; k < BSLfolders.size(); k++) {//iterate through already found BSL folders BSLlog << "testing " << dir_itr->path().filename() << " vs " << BSLfolders[k] << "\n"; if(dir_itr->path().filename() == BSLfolders[k]) { skip_folder = 1; BSLlog << "skipping " << BSLfolders[k] << " in " << pkg.modStringName << "\n"; break; } } if (!skip_folder && !exists("../GameDataFolder/IGMD/" + dir_itr->path().filename() + "/ignore.txt")) { remove_all( "../GameDataFolder/IGMD/" + dir_itr->path().filename() ); Sleep(100); create_directory( "../GameDataFolder/IGMD/" + dir_itr->path().filename()); BSLlog << "Copied " << dir_itr->path().string() << " in " << pkg.modStringName << "!\n"; for ( directory_iterator bsl_itr( dir_itr->path() ); bsl_itr != end_itr; bsl_itr++ ) { if ( bsl_itr->path().extension() == ".bsl" ) { copy_file(bsl_itr->path(), "../GameDataFolder/IGMD/" + dir_itr->path().filename() + "/" + bsl_itr->path().filename()); } } if( !pkg.hasAddon ) { BSLfolders.push_back( dir_itr->path().filename() ); //add back check for addon BSLlog << "Pushing " << dir_itr->path().filename() << "\n" ; } } } } } catch ( const std::exception & ex ) { setStatusArea("Warning, exception " + (string)ex.what() + "!"); while(1) Sleep(1000); } BSLlog.close(); } void writeInstalledMods(vector installedMods) { if ( exists( strInstallCfg ) ) { remove( strInstallCfg ); } ofstream file(strInstallCfg.c_str()); vectorlist = installedMods; vector::iterator begin_iter = list.begin(); vector::iterator end_iter = list.end(); sort( list.begin(), list.end() ); for( ; begin_iter != end_iter; ++begin_iter) { file << *begin_iter << " "; } file.close(); file.clear(); } vector getInstallString(string Cfg) { vector returnval; string line; fstream file; if (exists( Cfg )) { file.open(Cfg.c_str()); getline(file, line); tokenize(line, returnval); file.close(); file.clear(); sort(returnval.begin(), returnval.end()); } else cout << "fail"; return returnval; } /* GetUpdateStatus determines whether there is an update available. It is called once, *\ | on launch, by AEInstallerApp::OnInit(), and not only passes back a #defined result | | code, but also oversees the setting of data in the global structures currentAE and | | updateAE, which tell the Installer all the version information it needs to know. | | ---Return Values--- | | UPDATE_LOG_READ_ERR -- A log file could not be opened | | UPDATE_INST_REPL_ERR -- The Installer self-updating process failed | | UPDATE_MNTH_REQD_ERR -- The update is a patch, and the monthly release it | | patches is not installed | | UPDATE_NO_UPD_AVAIL -- Either there isn't an update in place, or it's not | | newer than what's installed | | UPDATE_SIMP_AVAIL -- An update is available | | UPDATE_GLOB_AVAIL -- An update is available that requires re-globalization | | afterwards (because of some notable change in the AE) | | UPDATE_INST_AVAIL -- An update is available that first requires the | | Installer to be replaced (when the new Installer | | launches, this function will be called again but will | | return UPDATE_SIMP_AVAIL or UPDATE_GLOB_AVAIL) | | UPDATE_PKG_AVAIL -- A newer version of individual package(s) is available | \* UPDATE_CONT_UPD -- Currently unused */ int GetUpdateStatus(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated) { fstream currentAECfg, updateAECfg, updateLog; string strInstaller = "Installer"; string strBeing = "being"; string strWas = "was"; // lol #ifdef WIN32 string strInstallerName = "AEInstaller.exe"; #else string strInstallerName = "Installer.app"; #endif // Try to get current AE's version info; if it doesn't exist, then the default version data for 2009-07 remains in place if (exists("packages/Globalize/Install_Info.cfg")) { currentAECfg.open("packages/Globalize/Install_Info.cfg"); if (!currentAECfg.fail()) { if (!ReadInstallInfoCfg(¤tAECfg, currentAE)) return UPDATE_LOG_READ_ERR; currentAECfg.close(); currentAECfg.clear(); } else return UPDATE_LOG_READ_ERR; } // Is there an update folder, and is it a monthly release or a patch? bool firstParty = 0; if (exists("../updates/Edition")) { firstParty = 1; } else { strEUFN = "Edition-patch"; if (exists("../updates/Edition-patch")) { firstParty = 1; } } if(firstParty) { // Unlike the current AE's version info, we *need* to find the update's version info or we won't continue string updateCfgPath = ("../updates/" + strEUFN + "/install/packages/Globalize/Install_Info.cfg"); updateAECfg.open(updateCfgPath.c_str()); if (!updateAECfg.fail()) { if (!ReadInstallInfoCfg(&updateAECfg, updateAE)) return UPDATE_LOG_READ_ERR; updateAECfg.close(); updateAECfg.clear(); } else return UPDATE_LOG_READ_ERR; // Now we check for an Installer update in progress if (exists("Update.log")) { updateLog.open("Update.log"); if (!updateLog.fail()) { vector lines; string line; int num_lines = 0; bool readingInstallerVersion = false, doneReadingFile = false; while (!updateLog.eof() && !doneReadingFile) { getline(updateLog, line); lines.push_back(line); num_lines++; vector tokens; vector::iterator iter; tokenize(line, tokens); iter = tokens.begin(); if (!readingInstallerVersion && tokens.capacity() >= 4) { if (!strInstaller.compare(*iter)) { if (!strBeing.compare(*++iter)) readingInstallerVersion = true; else if (!strWas.compare(*iter)) *installerJustUpdated = true; // our third indirect return value after currentAE and updateAE } } else if (readingInstallerVersion && tokens.capacity() >= 3) { readingInstallerVersion = false; string installerVersion = INSTALLER_VERSION; if (installerVersion.compare(*iter)) // then the shell script-powered replacement failed return UPDATE_INST_REPL_ERR; else { updateLog.close(); updateLog.clear(); Sleep(1000); remove("Update.log"); ofstream newUpdateLog("Update.log"); if (!newUpdateLog.fail()) { // Write over old log with updated information ptime startTime(second_clock::local_time()); string strStartTime = to_simple_string(startTime); string newUpdateLine = installerVersion + " on " + strStartTime; for (int a = 0; a < lines.capacity() - 2; a++) // if there were even lines in the log before this at all { newUpdateLog << lines[a].c_str(); newUpdateLog << "\n"; } newUpdateLog << "Installer was updated to:\n"; newUpdateLog << newUpdateLine.c_str(); *installerJustUpdated = true; // this value is indirectly returned to AEInstallerApp::OnInit() doneReadingFile = true; newUpdateLog.close(); newUpdateLog.clear(); //return UPDATE_CONT_UPD; // as noted above, we are not using this return value; in fact, we want... // ...the code to continue running down through the Edition version check } else return UPDATE_LOG_READ_ERR; } } } updateLog.close(); updateLog.clear(); } else return UPDATE_LOG_READ_ERR; } if (updateAE->AEVersion.compare(currentAE->AEVersion) >= 1) // is the release update newer than what's installed? { if (!strEUFN.compare("Edition-patch")) // if update is a patch... { if (currentAE->AEVersion.compare(updateAE->AEVersion.substr(0, updateAE->AEVersion.length() - 1))) // ...is it for a different month? return UPDATE_MNTH_REQD_ERR; } string strNewInstallerPath = "../updates/" + strEUFN + "/install/" + strInstallerName; string installerVersion = INSTALLER_VERSION; if (updateAE->InstallerVersion.compare(installerVersion) >= 1) { if (exists(strNewInstallerPath)) return UPDATE_INST_AVAIL; } else if (updateAE->globalizationRequired) return UPDATE_GLOB_AVAIL; else return UPDATE_SIMP_AVAIL; } } try { directory_iterator end; if (exists("../updates")) { for (directory_iterator install_iter("../updates"); install_iter != end; ++install_iter) { ModPackage installedPackage, updatePackage; if (is_directory(install_iter->path()) && exists(install_iter->path().string() + "/Mod_Info.cfg")) { fstream file; file.open((install_iter->path().string() + "/Mod_Info.cfg").c_str()); if (!file.fail()) updatePackage = fileToModPackage(file, install_iter->path().filename()); else { file.close(); continue; } if (exists("packages/" + install_iter->path().filename() + "/Mod_Info.cfg")) { file.close(); file.clear(); file.open(("packages/" + install_iter->path().filename() + "/Mod_Info.cfg").c_str()); if (!file.fail()) installedPackage = fileToModPackage(file, install_iter->path().filename()); file.close(); if (updatePackage.modStringVersion > installedPackage.modStringVersion) { if (updatePackage.installerVersion <= INSTALLER_VERSION) return UPDATE_PKG_AVAIL; } } else { file.close(); return UPDATE_PKG_AVAIL; } } } } } catch (exception & ex) { // setStatusArea("Warning, handled exception: " + (string)ex.what()); } return UPDATE_NO_UPD_AVAIL; } bool ReadInstallInfoCfg(fstream *fileHandler, Install_info_cfg *info_cfg) { vector tokens; vector::iterator iter; string line; string strAEVersion = "AE_Version"; string strInstallerVersion = "Installer_Version"; string strDaodanVersion = "Daodan_Version"; string strOniSplitVersion = "OniSplit_Version"; string strGUIWinVersion = "GUI_Win_Version"; string strGUIMacVersion = "GUI_Mac_Version"; string strReglobalize = "Reglobalize"; string strDeleteList = "Delete_List"; string strArrow = "->"; string strDoubleSlash = "//"; string strYes = "Yes"; // this is getting silly while (getline(*fileHandler, line)) { StripNewlines(&line); tokenize(line, tokens); iter = tokens.begin(); if (tokens.size() >= 3) { if (!strAEVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->AEVersion = *++iter; else return false; } else if (!strInstallerVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->InstallerVersion = *++iter; else return false; } else if (!strDaodanVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->DaodanVersion = *++iter; else return false; } else if (!strOniSplitVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->OniSplitVersion = *++iter; else return false; } else if (!strGUIWinVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->WinGUIVersion = *++iter; else return false; } else if (!strGUIMacVersion.compare(*iter)) { if (!strArrow.compare(*++iter)) info_cfg->MacGUIVersion = *++iter; else return false; } else if (!strReglobalize.compare(*iter)) { if (!strArrow.compare(*++iter)) { if (!strYes.compare(*++iter)) info_cfg->globalizationRequired = true; } else return false; } else if (!strDeleteList.compare(*iter)) { // We need to perform a totally customized parsing process on this data if (!strArrow.compare(*++iter)) { vector tokens2; tokenize(line, tokens2, ","); // the paths on this line are comma-delimited, so we parse it again vector::iterator iter2 = tokens2.begin(); string finalPath = ""; for (; iter2 != tokens2.end(); iter2++) { finalPath = finalPath + *iter2; string::size_type loc = finalPath.find("->", 0); // the first word will have "Delete_List ->" at the front, so let's cut that off if (loc != string::npos) finalPath = finalPath.substr(loc + 3, finalPath.size()); // If a path has '//' in it, it must contain some optional comments that were at the end of the Delete_List line loc = finalPath.find("//", 0); if (loc != string::npos) finalPath = finalPath.substr(0, loc); // Trim a single space if it exists at the start or finish; putting more than one space after a comma will break this if (finalPath.at(0) == ' ') finalPath = finalPath.substr(1, finalPath.size()); if (finalPath.at(finalPath.size() - 1) == ' ') finalPath = finalPath.substr(0, finalPath.size() - 1); // If the tokenized path ends with a '\', then we assume it was followed by a comma if (finalPath.at(finalPath.size() - 1) == '\\') { finalPath = finalPath.substr(0, finalPath.size() - 1); // clip the '\' off the end of the string now that it served its purpose... finalPath = finalPath + ","; // ...and add the actual comma back at the end } else // we can add the path to deleteList, and clear the path; otherwise it will be added to on the next iteration { if (StringIsLegalPathForDeletion(finalPath)) // ...and it's not violating any of our security rules as to what can be deleted... info_cfg->deleteList.push_back(finalPath); // ...then add it to our deleteList in memory finalPath.clear(); // clear the token we were building up before the next pass } } } else return false; } } tokens.clear(); } return true; } // TODO: Fix security holes here /* There is currently a security hole in this function; the first occurrence of a '.' not followed by a second '.' will prevent the function from noticing an actual occurrence of '..' later in the string; iow, it only looks after the first period it finds for a second period. A second hole is that the last slash will be trimmed from the path, but one could still use ".//" and it would get past this function, and possibly be interpreted by the Boost file functions as "the current directory". Iow, both of these checks need to be iterative, not one-time. Not too concerned about this at the moment, as only we of the AE Team are supplying the install_info file that this function is connected to. -I */ /* This function serves as a barrier against the Installer deleting files it shouldn't. *\ | It tests for each of the following conditions in the path it is passed: | | A. '..' as the whole path or '/..', '\..' anywhere in the path | | Reason: Moving up from the parent directory, the Edition folder, would allow one | | to delete anything on the hard drive, so all "parent path" references are illegal. | | B. '/' at the beginning of the path | | Reason: In Unix, this means the path starts from root level, as opposed to the | | directory we will evaluate these paths as being relative to, which is Edition/. | | C. '.' as the whole path | | Reason: This would mean "the Edition folder", which is not allowed to be deleted. | | D. 'GameDataFolder' at the end of the path | | Reason: We don't allow the entire GDF to be deleted, only specific files in it. | | E. '*' anywhere in the path | | Reason: We don't want this interpreted as a wildcard; it's best to only delete | *\ files by name. */ bool StringIsLegalPathForDeletion(string word) { string::size_type loc1, loc2; // Trim ending slashes in order to simplify the test // Note that we're only altering the local copy of the string here loc1 = word.find_last_of("\\", word.size()); if (loc1 == word.size() - 1) word.resize(word.size() - 1); loc1 = word.find_last_of("/", word.size()); if (loc1 == word.size() - 1) word.resize(word.size() - 1); // Test B loc1 = word.find_first_of("\\", 0); if (loc1 == 0) return false; // path begins with a slash, meaning root level of HD in Unix, an illegal path loc1 = word.find_first_of("/", 0); if (loc1 == 0) return false; // path begins with a slash, meaning root level of HD in Unix, an illegal path // Test E loc1 = word.find("*", 0); if (loc1 != string::npos) // if we found our character before reaching the end of the string return false; // path cannot contain the '*' character // Tests A (part 1) and C loc1 = word.find(".", 0); if (loc1 != string::npos) { if (word.size() == 1) return false; // path cannot be simply '.', referring to Edition folder itself loc2 = word.find(".", loc1 + 1); if (loc2 == loc1 + 1) // make sure this second period comes after the first one if (word.size() == 2) return false; // not allowed to reference a parent directory } // Test A (part 2) loc1 = word.find("/..", 0); if (loc1 != string::npos) return false; // not allowed to reference a parent directory loc1 = word.find("\\..", 0); if (loc1 != string::npos) return false; // not allowed to reference a parent directory // Test D loc1 = word.find("GameDataFolder", 0); if (loc1 == word.size() - 14) // if "GameDataFolder" is the last 14 characters of the string... return false; // not allowed to delete the GDF return true; } bool ProcessInstallerUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE) { ofstream file; string shellScript; ptime startTime(second_clock::local_time()); string strStartTime = to_simple_string(startTime); string progressMsg = "Installer being updated to:\n" + updateAE->InstallerVersion + " on " + strStartTime; file.open("Update.log"); if (!file.fail()) file << progressMsg.c_str(); file.close(); file.clear(); string popenCommand = "../updates/" + strEUFN + "/install/"; #ifdef WIN32 popenCommand = "replace_installer.bat"; #else // We can't just use '~' to mean "the home directory" because we need to check the path in C... // ...so we actually get the current user's shortname and manually construct the path to home FILE *fUserName = NULL; char chrUserName[32]; fUserName = popen("whoami", "r"); fgets(chrUserName, sizeof(chrUserName), fUserName); pclose(fUserName); string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh int endOfName = strUserName.find("\n", 0); string pathToTrash = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/"; tm tmStartTime = to_tm(startTime); pathToTrash = pathToTrash + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast(tmStartTime.tm_hour) + "-" + boost::lexical_cast(tmStartTime.tm_min) + "-" + boost::lexical_cast(tmStartTime.tm_sec); // lol create_directory(pathToTrash); // The script takes as a parameter the path the old Installer should go to, in quotes popenCommand = "bash " + popenCommand + "replace_installer.sh " + pathToTrash + "/Installer.app"; #endif file.close(); file.clear(); #ifdef WIN32 system(popenCommand.c_str()); #else popen(popenCommand.c_str(), "r"); #endif return true; // returning 'true' tells the Installer to quit itself ASAP so it can be replaced by the process that is now running } bool ProcessAEUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated) { fstream file; string line; vector tokens, updateStarted; string strInstaller = "Installer"; string strWas = "was"; string strPathToEUFN = ("../updates/" + strEUFN + "/"); // strEUFN is set by GetUpdateStatus() string strPathToEUFNInstall = ("../updates/" + strEUFN + "/install/"); string strPathToEUFNPackages = ("../updates/" + strEUFN + "/install/packages/"); string strPathToPackages = "packages/"; string strGlobalize = "Globalize/"; string strOniSplit = "OniSplit.exe"; string strDaodan = "binkw32.dll"; string strWinGUI = "onisplit_gui.exe"; string strWinGUILang = "ospgui_lang.ini"; string strMacGUI = "AETools.app"; #ifdef WIN32 string strOniApp = "Oni.exe"; #else string strOniApp = "Oni.app"; #endif bool needNewTrashDir = false; bool readingVerAndDate = false; #ifdef WIN32 string strTrashDir = "Trash\\"; // string unused in Windows because files are simply deleted #else FILE *fUserName = NULL; char chrUserName[32]; fUserName = popen("whoami", "r"); fgets(chrUserName, sizeof(chrUserName), fUserName); pclose(fUserName); string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh int endOfName = strUserName.find("\n", 0); string strTrashDir = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/"; #endif // Write to log that we are beginning the update process ptime startTime(second_clock::local_time()); string strStartTime = to_simple_string(startTime); string progressMsg = "\nEdition being updated to:\n" + updateAE->AEVersion + " on " + strStartTime; file.open("Update.log"); if (!file.fail()) file << progressMsg.c_str(); if (*installerJustUpdated) // then we want to know what folder in the Trash the Installer was placed in... { while (!file.eof()) // ...so we read the log to get the timestamp so we know the name of the folder that should be in the Trash { getline(file, line); tokenize(line, tokens); if (tokens.capacity() >= 4) if (!strInstaller.compare(tokens[0])) if (!strWas.compare(tokens[1])) readingVerAndDate = true; if (readingVerAndDate && tokens.capacity() >= 3) tokenize(tokens[2], updateStarted, "-"); } if (updateStarted.capacity() < 3) needNewTrashDir = true; else { strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "-" + updateStarted[0] + "-" + updateStarted[1] + "-" + updateStarted[2] + "/"; if (!exists(strTrashDir)) needNewTrashDir = true; } } #ifndef WIN32 if (!*installerJustUpdated || needNewTrashDir) // prepare a new directory for deleted files to go to { tm tmStartTime = to_tm(startTime); strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast(tmStartTime.tm_hour) + "-" + boost::lexical_cast(tmStartTime.tm_min) + "-" + boost::lexical_cast(tmStartTime.tm_sec) + "/"; create_directory(strTrashDir); } #endif file.close(); file.clear(); // Special code to replace our special files -- the Oni app, OniSplit, the Daodan DLL, and the GUI for OniSplit if (exists(strPathToEUFN + strOniApp)) { if (exists(strOniApp)) rename((path)strOniApp, (path)(strTrashDir + strOniApp)); rename((path)(strPathToEUFN + strOniApp), (path)strOniApp); } if (updateAE->OniSplitVersion.compare(currentAE->OniSplitVersion) >= 1) { if (exists(strPathToEUFNInstall + strOniSplit)) { if (exists(strOniSplit)) rename((path)strOniSplit, (path)(strTrashDir + strOniSplit)); rename((path)(strPathToEUFNInstall + strOniSplit), (path)strOniSplit); } } #ifdef WIN32 if (updateAE->DaodanVersion.compare(currentAE->DaodanVersion) >= 1) { if (exists(strPathToEUFN + strDaodan)) { if (exists(("../" + strDaodan))) rename((path)("../" + strDaodan), (path)(strTrashDir + strDaodan)); rename((path)(strPathToEUFN + strDaodan), (path)("../" + strDaodan)); } } if (updateAE->WinGUIVersion.compare(currentAE->WinGUIVersion) >= 1) { if (exists(strPathToEUFNInstall + strWinGUI)) { if (exists((path)strWinGUI)) rename((path)strWinGUI, (path)(strTrashDir + strWinGUI)); if (exists(strWinGUILang)) rename((path)strWinGUILang, (path)(strTrashDir + strWinGUILang)); rename((path)(strPathToEUFNInstall + strWinGUI), (path)strWinGUI); rename((path)(strPathToEUFNInstall + strWinGUILang), (path)strWinGUILang); } } #else if (updateAE->MacGUIVersion.compare(currentAE->MacGUIVersion) >= 1) { if (exists(strPathToEUFN + strMacGUI)) { if (exists(("../" + strMacGUI))) rename((path)("../" + strMacGUI), (path)(strTrashDir + strMacGUI)); rename((path)(strPathToEUFN + strMacGUI), (path)("../" + strMacGUI)); } } #endif // Now we trash whatever's in DeleteList; this allows us to clear out obsolete files in the previous AE install // Before moving a file to the Trash, we need to make sure each of the file's parent paths exists in the Trash... // ...so we iterate through the hierarchy of the file path, checking for each one and creating it if necessary for (vector::iterator iter = updateAE->deleteList.begin(); iter != updateAE->deleteList.end(); iter++) { string thePath = *iter; if (exists((path)("../" + thePath))) { string aParentPath; string::size_type curPos = thePath.find("/", 0); if (curPos != string::npos) aParentPath = thePath.substr(0, curPos); string::size_type lastPos = curPos; while (curPos != string::npos && curPos < thePath.size()) { aParentPath = aParentPath + thePath.substr(lastPos, curPos - lastPos); if (!exists(strTrashDir + aParentPath)) create_directory(strTrashDir + aParentPath); lastPos = curPos + 1; curPos = thePath.find("/", lastPos); aParentPath = aParentPath + "/"; } #ifndef WIN32 rename((path)("../" + thePath), (path)(strTrashDir + thePath)); #else remove((path)("../" + thePath)); #endif } } ProcessPackageUpdates(strPathToEUFNPackages, strPathToPackages); // Next, we get a list of which files and folders in the update's Globalize folder to move over; all files not starting with '.' will be moved... // ...and folders which do not exist in the current AE will be created there vector foldersToMake, filesToMove; string thePath; for (recursive_directory_iterator dir_itr(strPathToEUFNPackages + strGlobalize), end_itr; dir_itr != end_itr; ++dir_itr) { thePath = dir_itr->path().string(); MakePathLocalToGlobalize(&thePath); if (is_regular_file(dir_itr->status())) { if (dir_itr->filename().at(0) != '.') // skip over dot-files, which are invisible in Unix filesToMove.push_back(thePath); } else if (is_directory(dir_itr->status())) { if (!exists(strPathToPackages + strGlobalize + thePath)) foldersToMake.push_back(thePath); } } // Sort the foldersToMake strings by length, which is a fast solution to the problem of "How do we make sure we create folder 'parent/'... // ...before folder 'parent/child/'"? sort(foldersToMake.begin(), foldersToMake.end(), SortBySize); // SortBySize is a custom comparison function found later in this source file // First make the folders that don't exist in the current AE, so all the files have a place to go for (vector::iterator iter = foldersToMake.begin(); iter != foldersToMake.end(); iter++) { create_directory(strPathToPackages + strGlobalize + *iter); } for (vector::iterator iter = filesToMove.begin(); iter != filesToMove.end(); iter++) { if (exists(strPathToPackages + strGlobalize + *iter)) rename((path)(strPathToPackages + strGlobalize + *iter), (path)(strTrashDir + *iter)); rename((path)(strPathToEUFNPackages + strGlobalize + *iter), (path)(strPathToPackages + strGlobalize + *iter)); } // Clean up after ourselves, trashing any packages or programs in the update package that are not newer than the current AE create_directory(strTrashDir + "Unneeded update files"); rename((path)strPathToEUFN, (path)(strTrashDir + "Unneeded update files/" + strEUFN)); // Write to log that we are finished with update ptime end_time(second_clock::local_time()); string progressMsg2 = "Edition was updated to:\n" + updateAE->AEVersion + " on " + to_simple_string(end_time); file.open("Update.log"); if(!file.fail()) file.write(progressMsg2.c_str(), sizeof(progressMsg2.c_str())); file.close(); file.clear(); if (updateAE->globalizationRequired) CheckForGlobalization(true); // the 'true' value forces re-globalization globalPackages = getPackages(); // refresh the list in memory wxCommandEvent e; TheWindow->OnRefreshButtonClick( e ); return true; } void ProcessPackageUpdates(string pathToUpdate, string strPathToPackages) { ptime startTime(second_clock::local_time()); #ifdef WIN32 string strTrashDir = "Trash\\"; // string unused in Windows because files are simply deleted #else FILE *fUserName = NULL; char chrUserName[32]; fUserName = popen("whoami", "r"); fgets(chrUserName, sizeof(chrUserName), fUserName); pclose(fUserName); string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh int endOfName = strUserName.find("\n", 0); string strTrashDir = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/"; bool needNewTrashDir = true; tm tmStartTime = to_tm(startTime); #endif try { directory_iterator end; for (directory_iterator update_iter(pathToUpdate); update_iter != end; ++update_iter) { ModPackage installedPackage, updatePackage; string updtPath = update_iter->path().string(); string updtFolder = update_iter->path().filename(); string updtModInfo = updtPath + "/Mod_Info.cfg"; string instModInfo = strPathToPackages + "/" + updtFolder + "/Mod_Info.cfg"; if (!boost::iequals(updtFolder, "Edition") && !boost::iequals(updtFolder, "Edition-patch") && is_directory(update_iter->path()) && exists(updtModInfo)) { fstream file; file.open((updtModInfo).c_str()); if (!file.fail()) updatePackage = fileToModPackage(file, updtFolder); else { file.close(); continue; } if (exists(instModInfo)) { file.close(); file.clear(); file.open(instModInfo.c_str()); if (!file.fail()) { installedPackage = fileToModPackage(file, updtFolder); } file.close(); } file.close(); if (updatePackage.modStringVersion > installedPackage.modStringVersion) { if (updatePackage.installerVersion <= INSTALLER_VERSION) { if(exists(strPathToPackages + "/" + updatePackage.modStringName)) { #ifdef WIN32 remove_all((path)(strPathToPackages + "/" + updatePackage.modStringName)); #else if (needNewTrashDir) { strTrashDir = strTrashDir + "Old_packages_" + boost::lexical_cast(tmStartTime.tm_hour) + "-" + boost::lexical_cast(tmStartTime.tm_min) + "-" + boost::lexical_cast(tmStartTime.tm_sec) + "/"; create_directory(strTrashDir); needNewTrashDir = false; } rename((path)(strPathToPackages + "/" + updatePackage.modStringName), (path)(strTrashDir + updatePackage.modStringName)); #endif } rename((path)(pathToUpdate + "/" + updatePackage.modStringName), (path)(strPathToPackages + "/" + updatePackage.modStringName)); } } } } } catch (exception & ex) { setStatusArea("Warning, handled exception: " + (string)ex.what()); } wxCommandEvent e; TheWindow->OnRefreshButtonClick( e ); } /* MakePathLocalToGlobalize is a function used once by ProcessAEUpdate() that takes a file in an \ | update's Globalize folder and changes its path, originally relative to the Installer, to be | | relative to the structure of the Globalize folder; this makes it easier to have the Installer | \ move said file from an update's Globalize folder to the current Globalize folder. */ void MakePathLocalToGlobalize(string *installerBasedPath) { int deleteToHere = 0; deleteToHere = installerBasedPath->find("Globalize/"); if (deleteToHere != 0) { deleteToHere += strlen("Globalize/"); installerBasedPath->erase(0, deleteToHere); } } /* SortBySize is a custom comparison function that we call when using the C++ sort() function in \ \ ProcessAEUpdate() on some strings that we want to sort by length. */ bool SortBySize(string a, string b) { return (a.size() < b.size()); } //stolen token function... void tokenize(const string& str, vector& tokens, const string& delimiters) { // Skip delimiters at beginning. string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Find first "non-delimiter". string::size_type pos = str.find_first_of(delimiters, lastPos); while (string::npos != pos || string::npos != lastPos) { // Found a token, add it to the vector. tokens.push_back(str.substr(lastPos, pos - lastPos)); // Skip delimiters. Note the "not_of" lastPos = str.find_first_not_of(delimiters, pos); // Find next "non-delimiter" pos = str.find_first_of(delimiters, lastPos); } } /* StripNewlines() gets rids of any linebreaks that come from text returned by getline(); \ | getline() should be stripping those out, but Windows CR/LF files seem to be sneaking | \ some extra return characters into strings in the ReadInstallInfoCfg() function. */ void StripNewlines(string *theLine) { int deleteFromHere = 0; deleteFromHere = theLine->find("\r"); if (deleteFromHere > 0) theLine->erase(deleteFromHere, theLine->size()); } void clearOldDats(void) { directory_iterator end_iter_gdf; for ( directory_iterator dir_itr_gdf( "../GameDataFolder" ); dir_itr_gdf != end_iter_gdf; ++dir_itr_gdf ) { if ( dir_itr_gdf->path().extension() == ".dat" || dir_itr_gdf->path().extension() == ".raw" || dir_itr_gdf->path().extension() == ".sep" ) { remove( dir_itr_gdf->path() ); } } } // this function copies files and directories. If copying a // directory to a directory, it copies recursively. //pardon the mess, I did this at midnight, and had to fix a bug void copy( const path & from_ph, const path & to_ph ) { cout << to_ph.string() << "\n"; // Make sure that the destination, if it exists, is a directory if((exists(to_ph) && !is_directory(to_ph)) || (!exists(from_ph))) cout << "error"; if(!is_directory(from_ph)) { if(exists(to_ph)) { copy_file(from_ph,to_ph/from_ph.filename()); } else { try{ copy_file(from_ph,to_ph); } catch (exception ex){ cout << from_ph.string() << " to " << to_ph.string() << "\n"; } } } else if(from_ph.filename() != ".svn") { path destination; if(!exists(to_ph)) { destination=to_ph; } else { destination=to_ph/from_ph.filename(); } for(directory_iterator i(from_ph); i!=directory_iterator(); ++i) { //the idiot who coded this in the first place (not me) //forgot to make a new directory. Exception city. x_x create_directory(destination); copy(*i,destination/i->filename()); } } } void copy_directory( const path &from_dir_ph, const path &to_dir_ph) { if(!exists(from_dir_ph) || !is_directory(from_dir_ph) || exists(to_dir_ph)) cout << !exists(from_dir_ph) << " " << !is_directory(from_dir_ph) << " " << exists(to_dir_ph); # ifdef BOOST_POSIX struct stat from_stat; if ( (::stat( from_dir_ph.string().c_str(), &from_stat ) != 0) || ::mkdir(to_dir_ph.native_directory_string().c_str(), from_stat.st_mode)!=0) # endif } string escapePath(string input) { string output; string escape_me = "& ;()|<>\"'\\#*?$"; for (unsigned int i = 0; i < input.size(); i++) { for (unsigned int j = 0; j < escape_me.size(); j++) if (input[i] == escape_me[j]) output += '\\'; output += input[i]; } return output; } Install_info_cfg::Install_info_cfg() { AEVersion = "2009-07b"; InstallerVersion = "1.0.1"; DaodanVersion = "1.0"; OniSplitVersion = "0.9.38.0"; WinGUIVersion = "0"; MacGUIVersion = "0"; patch = false; globalizationRequired = false; deleteList.reserve(255); } ModPackage::ModPackage() { isInstalled = true; // replace with function name = ""; modStringName = ""; modStringVersion = 0; platform = "Both"; hasOnis = false; hasDeltas = false; hasBSL = false; hasAddon = false; hasDats = false; category = ""; creator = ""; isEngine = false; readme = ""; globalNeeded = true; } #ifndef WIN32 void Sleep(int ms) { sleep(ms / 1000); } #endif #ifdef WIN32 void RedirectIOToConsole() { int hConHandle; long lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; // allocate a console for this app AllocConsole(); // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); // redirect unbuffered STDOUT to the console lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stdout = *fp; setvbuf( stdout, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; setvbuf( stdin, NULL, _IONBF, 0 ); // redirect unbuffered STDERR to the console lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stderr = *fp; setvbuf( stderr, NULL, _IONBF, 0 ); // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog // point to console as well ios::sync_with_stdio(); } #endif