source: AE/Installer/trunk/source/installer.cpp@ 503

Last change on this file since 503 was 502, checked in by iritscen, 15 years ago

Added optional OS flag, "Platform".

File size: 61.5 KB
RevLine 
[487]1/***************************************************************************\
2| Project: AE Installer |
3| By: Gumby & Iritscen |
4| File: Installer.cpp |
5| Function: Contains the real meat of the installation process. Mm, beefy. |
6| Created: 24/05/2009 19:39:00 |
7\***************************************************************************/
[325]8
[487]9// TODO: Load credits from text resource file
10// TODO: Clear mod info fields when mod is de-selected
11
[489]12//#define DEBUG
13#ifdef WIN32
14//#include <windows.h>
15#define popen _popen
16#endif
[487]17#include "boost/date_time/gregorian/gregorian.hpp"
18#include "boost/date_time/date_parsing.hpp"
19#include "boost/date_time/posix_time/posix_time.hpp"
[325]20#include "installer.h"
[487]21#include "aeinstallerapp.h"
[325]22
[487]23using namespace boost::gregorian;
24using namespace boost::posix_time;
[325]25
[487]26// externs declared in installer.h
27string strInstallCfg = "../GameDataFolder/Add.cfg";
28string strEUFN = "Edition"; // GetUpdateStatus() may set this to "Edition-patch" later, but this is the assumed name of the new Edition folder in Updates/
[325]29
[487]30int globalizeData(void)
[325]31{
[487]32 busy = 1;
33 using boost::lexical_cast;
34 using boost::bad_lexical_cast;
35 using namespace boost::gregorian;
36 using namespace boost::posix_time;
37 ptime start_time(second_clock::local_time());
38
39 setStatusArea("Globalizing!");
[325]40 int err = 0;
[487]41 int parts_done = 0;
42 remove("Globalize.log");
43 ofstream logfile("Globalize.log");
44 logfile << "Globalization started " << to_simple_string(start_time) << endl;
45
46 try { // the levels Oni has...probably should have made a string array. Oops.
47 char levels_cstr[15][3] = {"0", "1", "2", "3", "4", "6", "8", "9", "10", "11", "12", "13", "14", "18", "19"};
48 vector<string> levels;
49 for (int f = 0; f < 15; f++) {
50 levels.push_back(levels_cstr[f]);
[325]51 }
52
53 path Characters = "../GameDataFolder/level0_Characters";
54 path Particles = "../GameDataFolder/level0_Particles";
55 path Archive = "../GameDataFolder/Archive";
56 path Textures = "../GameDataFolder/level0_Textures";
57 path Sounds = "../GameDataFolder/level0_Sounds";
58 path Animations = "../GameDataFolder/level0_Animations";
59 path TRAC = Animations / "level0_TRAC";
60 path TRAM = Animations / "level0_TRAM";
[487]61
62 vector<path> GDFPaths;
63 GDFPaths.push_back(Particles);
64 GDFPaths.push_back(Textures);
65 GDFPaths.push_back(Sounds);
66 GDFPaths.push_back(TRAC);
67 GDFPaths.push_back(TRAM);
68
69 path VanillaCharacters = "VanillaDats/level0_Final/level0_Characters/level0_Characters.oni";
70 path VanillaParticles = "VanillaDats/level0_Final/level0_Particles/level0_Particles.oni";
71 path VanillaTextures = "VanillaDats/level0_Final/level0_Textures/level0_Textures.oni";
72 path VanillaSounds = "VanillaDats/level0_Final/level0_Sounds/level0_Sounds.oni";
73 path VanillaAnimations = "VanillaDats/level0_Final/level0_Animations/level0_Animations.oni";
74 path VanillaTRAC = "VanillaDats/level0_Final/level0_Animations/level0_TRAC.oni";
75 path VanillaTRAM = "VanillaDats/level0_Final/level0_Animations/level0_TRAM.oni";
76
77 vector<path> VanillaPaths;
78
79 VanillaPaths.push_back(VanillaParticles);
80 VanillaPaths.push_back(VanillaTextures);
81 VanillaPaths.push_back(VanillaSounds);
82 VanillaPaths.push_back(VanillaTRAC);
83 VanillaPaths.push_back(VanillaTRAM);
84
85 setStatusArea("Removing old GameDataFolder...\n");
86 logfile << "Removing old GameDataFolder...\n";
87 remove_all( "../GameDataFolder/" );
88 setStatusArea("Creating needed directories...");
89 logfile << "Creating needed directories...\n";
[325]90 create_directory( "../GameDataFolder/" );
[487]91
[325]92 create_directory( "packages" );
93
[487]94 if (exists("VanillaDats")) remove_all("VanillaDats");
95 create_directory( "VanillaDats" );
96 create_directory( "VanillaDats/level0_Final/" );
[325]97 create_directory( Characters );
98 create_directory( Particles );
99 create_directory( Archive );
100 create_directory( Textures );
101 create_directory( Sounds );
102 create_directory( Animations );
103 create_directory( TRAC );
104 create_directory( TRAM );
[487]105 int num_levels = 0;
106 for(int i = 1; i < 15; i++)
107 {
108 if (exists("../../GameDataFolder/level" + levels[i] + "_Final.dat")) {
109 num_levels++;
110
111 }
112 }
113 logfile << "Exporting and moving...\n\n";
114 int total_steps = 8 + 2 * num_levels;
[325]115
116 for(int i = 0; i < 15; i++)
[487]117 {
118 if (exists("../../GameDataFolder/level" + levels[i] + "_Final.dat")) {
119 logfile << "level" << levels[i] << "_Final\n";
120 logfile << "\tExporting level" << levels[i] << "_Final.dat\n";
121 setStatusArea("Step " + lexical_cast<std::string>(parts_done + 1) + "/" + lexical_cast<std::string>(total_steps) + " exporting level" + levels[i]+"_Final.dat");
122 create_directory( "../GameDataFolder/level" + levels[i] + "_Final" );
123 system((strOniSplit + " -export ../GameDataFolder/level" + levels[i] + "_Final ../../GameDataFolder/level" + levels[i] + "_Final.dat").c_str());
124 create_directory( "VanillaDats/level" + levels[i] + "_Final" );
125 create_directory( "VanillaDats/level" + levels[i] + "_Final/level" + levels[i] + "_Final" );
126
127 //Moves the AKEV and other files into a safe directory so that level specific textures are not globalized...
128 if ( strcmp(levels[i].c_str(), "0") ){
129 create_directory( "../GameDataFolder/level" + levels[i] + "_Final/AKEV" );
130 system((strOniSplit + " -move:overwrite ../GameDataFolder/level" + levels[i] + "_Final/AKEV ../GameDataFolder/level" + levels[i] + "_Final/AKEV*.oni").c_str());
131
132 }
133
134 directory_iterator end_iter;
135 for ( directory_iterator dir_itr( "../GameDataFolder/level" + levels[i] + "_Final" ); dir_itr != end_iter; ++dir_itr )
[325]136 {
[487]137 if ( is_regular_file( dir_itr->status() ) )
[325]138 {
[487]139 if ( dir_itr->path().filename().substr(0,8) == "TXMPfail" ||
140 dir_itr->path().filename().substr(0,9) == "TXMPlevel" ||
141 ( dir_itr->path().filename().substr(0,4) == "TXMP" && dir_itr->path().filename().find("intro")!=string::npos) ||
142 dir_itr->path().filename().substr(0,4) == "TXMB" ||
143 dir_itr->path().filename() == "M3GMpowerup_lsi.oni" ||
144 dir_itr->path().filename() == "TXMPlsi_icon.oni" ||
145 ( dir_itr->path().filename().substr(0,4) == "TXMB" && dir_itr->path().filename().find("splash_screen.oni")!=string::npos) )
146 {
147 cout <<dir_itr->path().filename() << "\n";
148 create_directory( dir_itr->path().parent_path() / "NoGlobal");
149 if(!exists( dir_itr->path().parent_path() / "NoGlobal" / dir_itr->filename())) rename(dir_itr->path(), dir_itr->path().parent_path() / "NoGlobal" /
150 dir_itr->filename());
151 else remove(dir_itr->path());
152 }
153 else if (dir_itr->path().filename().substr(0,4) == "TRAC"
154 ) {
155 cout <<dir_itr->path().filename() << "\n";
156 if(!exists( TRAC / dir_itr->filename())) rename(dir_itr->path(), TRAC / dir_itr->filename());
157 else remove(dir_itr->path());
158 }
159 else if (dir_itr->path().filename().substr(0,4) == "TRAM") {
160 cout <<dir_itr->path().filename() << "\n";
161 if(!exists( TRAM / dir_itr->filename())) rename(dir_itr->path(), TRAM / dir_itr->filename());
162 else remove(dir_itr->path());
163 }
164 else if (dir_itr->path().filename().substr(0,4) == "ONSK" ||
165 dir_itr->path().filename().substr(0,4) == "TXMP") {
166 cout <<dir_itr->path().filename() << "\n";\
167 create_directory( dir_itr->path().parent_path() / "TexFix");
168 if(!exists( Textures / dir_itr->filename())) rename(dir_itr->path(), Textures / dir_itr->filename());
169 }
170 else if (dir_itr->path().filename().substr(0,4) == "ONCC"
171 || dir_itr->path().filename().substr(0,4) == "TRBS"
172 || dir_itr->path().filename().substr(0,4) == "ONCV"
173 || dir_itr->path().filename().substr(0,4) == "ONVL"
174 || dir_itr->path().filename().substr(0,4) == "TRMA"
175 || dir_itr->path().filename().substr(0,4) == "TRSC"
176 || dir_itr->path().filename().substr(0,4) == "TRAS") {
177 cout <<dir_itr->path().filename() << "\n";
178 if(!exists( Characters / dir_itr->filename())) rename(dir_itr->path(), Characters / dir_itr->filename());
179 else remove(dir_itr->path());
180 }
181 else if (dir_itr->path().filename().substr(0,4) == "OSBD"
182 || dir_itr->path().filename().substr(0,4) == "SNDD") {
183 cout << dir_itr->path().filename() << "\n";
184 if(!exists( Sounds / dir_itr->filename())) rename(dir_itr->path(), Sounds / dir_itr->filename());
185 else remove(dir_itr->path());
186 }
187 else if (dir_itr->path().filename().substr(0,5) == "BINA3"
188 || dir_itr->path().filename().substr(0,10) == "M3GMdebris"
189 || dir_itr->path().filename() == "M3GMtoxic_bubble.oni"
190 || dir_itr->path().filename().substr(0,8) == "M3GMelec"
191 || dir_itr->path().filename().substr(0,7) == "M3GMrat"
192 || dir_itr->path().filename().substr(0,7) == "M3GMjet"
193 || dir_itr->path().filename().substr(0,9) == "M3GMbomb_"
194 || dir_itr->path().filename() == "M3GMbarab_swave.oni"
195 || dir_itr->path().filename() == "M3GMbloodyfoot.oni"
196 ){
197 cout <<dir_itr->path().filename() << "\n";
198 if(!exists( Particles / dir_itr->filename())) rename(dir_itr->path(), Particles / dir_itr->filename());
199 else remove(dir_itr->path());
200 }
201 else if (dir_itr->path().filename().substr(0,4) == "AGDB"
202 || dir_itr->path().filename().substr(0,4) == "TRCM") {
203 cout <<dir_itr->path().filename() << "\n";
204
205 if(!exists( Archive / dir_itr->filename())) rename(dir_itr->path(), Archive / dir_itr->filename());
206 else remove(dir_itr->path());
207 }
208 else if (dir_itr->path().filename().substr(0,4) == "ONWC") { //fix for buggy ONWC overriding
209 cout <<dir_itr->path().filename() << "\n";
210
211 if(!exists( "VanillaDats/level0_Final/level0_Final/" + dir_itr->filename()))
212 rename(dir_itr->path(), "VanillaDats/level0_Final/level0_Final/" + dir_itr->filename());
213 else remove(dir_itr->path());
214 }
[325]215
[487]216 if (exists(dir_itr->path())) {
217
218 }
219 else {
220 //logfile << "\tMoved file: " << dir_itr->path().filename() << "\n";
221 }
[325]222 }
[487]223
224
225
[325]226 }
227
[487]228 logfile << "\tCleaning up TXMPs...\n";
229 system( (strOniSplit + " -move:delete " + Textures.string() + " ../GameDataFolder/level" + levels[i] + "_Final/TXMP*.oni").c_str());
[325]230
[487]231
232 if ( strcmp(levels[i].c_str(), "0") ){
233 system((strOniSplit + " -move:overwrite ../GameDataFolder/level" + levels[i] + "_Final ../GameDataFolder/level" + levels[i] + "_Final/AKEV/AKEV*.oni").c_str());
234 remove( "../GameDataFolder/level" + levels[i] + "_Final/AKEV" );
235 }
236
237 parts_done++;
238
239 setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) ));
240
[325]241 }
242 }
[487]243 logfile << "Reimporting levels\n";
[325]244 for (int i = 0; i < 15; i++)
245 {
[487]246 logfile << "\tReimporting level" << levels[i] << "_Final.oni\n";
247 setStatusArea("Step " + lexical_cast<std::string>(parts_done + 1) + "/" + lexical_cast<std::string>(total_steps) + " reimporting level" + levels[i]+"_Final.oni");
248 logfile << (strOniSplit + " " + strImportOption + " ../GameDataFolder/level" + levels[i] + "_Final VanillaDats/level" + levels[i] + "_Final/level"
249 + levels[i] + "_Final/level" + levels[i] + "_Final.oni >> Globalize.log").c_str() << '\n';
250 string sys_str = (strOniSplit + " " + strImportOption + " ../GameDataFolder/level" + levels[i] + "_Final VanillaDats/level" + levels[i] + "_Final/level"
251 + levels[i] + "_Final/level" + levels[i] + "_Final.oni");
252 system(sys_str.c_str() );
253 setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) ));
254 parts_done++;
[325]255 }
256 create_directory( VanillaParticles.parent_path() );
257 create_directory( VanillaTextures.parent_path() );
258 create_directory( VanillaSounds.parent_path() );
259 create_directory( VanillaAnimations.remove_filename() );
260
[487]261 for(unsigned int j = 0; j < GDFPaths.size(); j++) {
262 logfile << "\tReimporting " << GDFPaths[j].filename() << ".oni\n";
263 setStatusArea("Step " + lexical_cast<std::string>(parts_done + 1) + "/" + lexical_cast<std::string>(total_steps) + ": reimporting " + GDFPaths[j].filename() );
264 system((strOniSplit + " " + strImportOption + " " + GDFPaths[j].string() + " " + VanillaPaths[j].string()).c_str());
265 parts_done++;
266 setProgressBar( (int)(1000 * (float)(parts_done) / (float)(total_steps) ));
267 }
268 logfile << "\nMoving level0_Characters\n";
269 setStatusArea("Step " + lexical_cast<std::string>(parts_done + 1) + "/" + lexical_cast<std::string>(total_steps) + ": moving level0_Characters" );
270 copy((path)"../GameDataFolder/level0_Characters", (path)("VanillaDats/level0_Final"));
271 GDFPaths.push_back( Characters );
272 for(int i = 0; i < GDFPaths.size(); i++)
273 {
274 directory_iterator end_iter;
275 for ( directory_iterator dir_itr( GDFPaths[i] ); dir_itr != end_iter; ++dir_itr )
[325]276 {
[487]277 try
[325]278 {
[487]279 rename(dir_itr->path(), "../GameDataFolder/level0_Final/" + dir_itr->path().filename() );
[325]280 }
[487]281 catch(exception &ex) {
282
283 }
[325]284 }
285 }
286
[487]287 create_directory((path)"../GameDataFolder/IGMD");
288 copy((path)"packages/VanillaBSL/IGMD", (path)"../GameDataFolder");
289 setProgressBar( 1000 );
290
291 if(exists("../../persist.dat"))
292 if(!exists("../persist.dat"))
[325]293
[487]294 //TODO: Concatenate level0 Dirs.
[325]295
[487]296 copy("../../persist.dat","..");
297 if(exists("../../key_config.txt"))
298 if(!exists("../key_config.txt"))
299 copy("../../key_config.txt","..");
300
301#ifndef WIN32
302 /* 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).
303 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
304 run Oni before :-p */
[500]305 string fullAEpath = escapePath(system_complete(".").parent_path().parent_path().string()); // get full path for Edition/ (Oni wants the folder that *contains* the GDF)
[487]306 char prefsCommand[300] = "[ -f ~/Library/Preferences/com.godgames.oni.plist ] && defaults write com.godgames.oni RetailInstallationPath -string '";
[500]307 strcat(prefsCommand, fullAEpath.c_str());
[487]308 strcat(prefsCommand, "'"); // path string is enclosed in single quotes to avoid the need to escape UNIX-unfriendly characters
309 system(prefsCommand);
310#endif
311
312 setStatusArea((string)"Done! Now select your mod packages and click install.");
[325]313 }
[487]314 catch (exception & ex) {
315 setStatusArea("Warning, handled exception: " + (string)ex.what());
316 }
[325]317
[487]318 ptime end_time(second_clock::local_time());
319 time_period total_time (start_time, end_time);
320 logfile << "\n\nGlobalization ended " << to_simple_string(end_time) << "\nThe process took " << total_time.length();
321 logfile.close();
322 busy = 0;
[325]323 return err;
324}
325
[487]326vector<ModPackage> getPackages(string packageDir)
[325]327{
328 vector<ModPackage> packages;
[500]329 ModPackage package;
[487]330 packages.reserve(256);
[325]331 fstream file;
332 string filename = "\0";
333 string MODINFO_CFG = "Mod_Info.cfg";
334
335 try
336 {
[487]337 for (directory_iterator dir_itr(packageDir), end_itr; dir_itr != end_itr; ++dir_itr)
[325]338 {
339 file.open((dir_itr->path().string() + "/" + MODINFO_CFG).c_str());
340
[500]341 if (!file.fail())
[325]342 {
[500]343 package = fileToModPackage(file);
344 if (package.installerVersion.compare(INSTALLER_VERSION) < 1) // if mod requires newer version of the Installer, we won't add it to the list
[502]345 {
346#ifdef WIN32
347 if (!package.platform.compare("Windows") || !package.platform.compare("Both")) // don't show package if it's not for the right OS
348#else
349 if (!package.platform.compare("Macintosh") || !package.platform.compare("Both"))
350#endif
351 packages.push_back(package);
352 }
[325]353 }
354 file.close();
355 file.clear();
356 }
[487]357 sort(packages.begin(), packages.end());
[325]358 }
359 catch (const std::exception & ex)
360 {
361 cout << "Warning, something odd happened!\n";
362 }
363
364 return packages;
365}
366
367ModPackage fileToModPackage(fstream &file)
368{
369 /*
370 This converts a file to a ModPackage struct.
371
372 A few notes...
373 "iter" is the current word we are on. I should have named it "token" or something, but I don't have multiple iterators, so its ok.
374 I refer to (*iter) at the beginning of each if statement block. I could probably store it as a variable, but I'm pretty sure that dereferencing a pointer\iterator isn't much
375 slower than reading a variable.
376 */
377 ModPackage package;
378 string line;
[500]379 static string AEInstallVersion = "AEInstallVersion"; // used for comparing to the current token...
380 static string NameOfMod = "NameOfMod";
381 static string ARROW = "->";
382 static string ModString = "ModString";
[502]383 static string Platform = "Platform";
[325]384 static string HasOnis = "HasOnis";
385 static string HasDeltas = "HasDeltas";
386 static string HasBSL = "HasBSL";
387 static string HasDats = "HasDats";
388 static string IsEngine = "IsEngine";
389 static string Readme = "Readme";
390 static string GlobalNeeded = "GlobalNeeded";
391 static string Category = "Category";
392 static string Creator = "Creator";
[500]393 while (!file.eof())
[325]394 {
[500]395 getline(file,line);
[325]396 vector<string> tokens;
397 vector<string>::iterator iter;
[500]398 tokenize(line, tokens);
399 if (tokens.capacity() >= 3)
400 {
401 iter = tokens.begin();
402
403 if (!AEInstallVersion.compare(*iter))
404 {
405 iter++; iter++;
406 package.installerVersion = *iter;
407 }
408 else if (!NameOfMod.compare(*iter))
409 {
410 for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++) // iterates through the words, ends if it reaches the end of the line or a "//" comment
411 {
412 if (ARROW.compare(*iter) && NameOfMod.compare(*iter)) // ignores "->" and "NameOfMod"
[325]413 package.name += *iter + " ";
414 }
415 }
[500]416 else if (!ModString.compare(*iter))
417 {
[325]418 iter++; iter++;
419 package.modStringName = *iter;
420 iter++;
421 package.modStringVersion = atoi((*iter).c_str());
422 }
[502]423 else if (!Platform.compare(*iter))
424 {
425 iter++; iter++;
426 package.platform = *iter;
427 }
[500]428 else if (!HasOnis.compare(*iter))
429 {
[325]430 iter++; iter++;
[500]431 if (boost::iequals(*iter, "Yes")) package.hasOnis = 1;
[487]432 }
[500]433 else if (!HasBSL.compare(*iter))
434 {
435 iter++; iter++;
436 if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.hasBSL = true;
437 else if (boost::iequals(*iter, "Addon")) package.hasAddon = true;
[325]438 }
[500]439 else if (!HasDeltas.compare(*iter))
440 {
[325]441 iter++; iter++;
[500]442 if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.hasDeltas = 1;
[325]443 }
[500]444 else if (!HasDats.compare(*iter))
445 {
[325]446 iter++; iter++;
[500]447 if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.hasDats = 1;
[325]448 }
[500]449 else if (!IsEngine.compare(*iter))
450 {
[325]451 iter++; iter++;
[500]452 if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.isEngine = 1;
[325]453 }
[500]454 else if (!GlobalNeeded.compare(*iter))
455 {
[325]456 iter++; iter++;
[500]457 if (toupper((*iter)[0]) == 'Y' && toupper((*iter)[1]) == 'E' && toupper((*iter)[2]) == 'S') package.globalNeeded = 1;
458 else if (toupper((*iter)[0]) == 'N' && toupper((*iter)[1]) == 'O') package.globalNeeded = 1; // only place where checking for "No" is important atm
[325]459 }
[500]460 else if (!Category.compare(*iter))
461 {
462 for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++)
463 {
464 if (ARROW.compare(*iter) && Category.compare(*iter)) // ignores "->" and "Category"
[325]465 package.category += *iter + " ";
466 }
467 }
[500]468 else if (!Creator.compare(*iter))
469 {
470 for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++)
471 {
472 if (ARROW.compare(*iter) && Creator.compare(*iter)) // ignores "->" and "Creator"
[325]473 package.creator += *iter + " ";
474 }
475 }
[500]476 else if (!Readme.compare(*iter))
477 {
478 for ( ; iter !=tokens.end() && SLASHSLASH.compare(*iter); iter++)
479 {
480 if (ARROW.compare(*iter) && Readme.compare(*iter)) // ignores "->" and "Readme"
481 {
482 if (!(*iter).compare("\\n")) package.readme += '\n';
[325]483 else package.readme += *iter + " ";
484 }
485 }
486 }
487 }
488 }
[487]489
[325]490 return package;
491}
492
493void recompileAll(vector<string> installedMods)
[487]494{try {
495 busy = 1;
496 using namespace boost::gregorian;
497 using namespace boost::posix_time;
498 using boost::lexical_cast;
499 using boost::bad_lexical_cast;
500 path vanilla_dir = "./VanillaDats/";
[325]501 string importCommand = "";
502 int numberOfDats = 0;
503 int j = 1;
504 string datString;
[487]505
506 setStatusArea("Importing levels...");
507
[325]508 std::stringstream out;
509
[487]510 ptime start_time(second_clock::local_time());
511 clearOldDats();
[325]512
[487]513 if(exists("Install.log")) remove("Install.log");
514 ofstream logfile("Install.log");
515 logfile << "Mod Installation started " << to_simple_string(start_time) << endl;
516 logfile.close();
517
518 if(splitInstances == true)
519 {
[325]520 recursive_directory_iterator end_iter;
521
522 for ( recursive_directory_iterator dir_itr( vanilla_dir );
523 dir_itr != end_iter;
524 ++dir_itr )
525 {
526 try{
527 if ( is_directory( dir_itr->status() ) && dir_itr.level() == 1)
528 {
529 numberOfDats++;
530 }
531 }
[487]532 catch(exception & ex) {
533 remove("Install.log");
534 ofstream logfile("Install.log");
[325]535
[487]536 logfile << "Warning, exception " << ex.what() << "!";
537 setStatusArea("Warning, exception " + (string)ex.what() + "!");
538 logfile.close();
[325]539 }
540 }
541 try {
[487]542 out << numberOfDats;
543 datString = out.str();
[325]544 for ( recursive_directory_iterator dir_itr( vanilla_dir );
545 dir_itr != end_iter;
546 ++dir_itr )
547 {
548 try
549 {
550 if ( is_directory( dir_itr->status() ) && dir_itr.level() == 1)
551 {
552 importCommand = strOniSplit + " " + strImportOption + " " + dir_itr->path().parent_path().string() + '/' + dir_itr->path().filename();
[487]553 for (unsigned int i = 0; i < installedMods.size(); ++i) {
[325]554 if (exists("packages/" + installedMods[i] + "/oni/" + dir_itr->path().parent_path().filename() + '/' + dir_itr->path().filename() ))
555 importCommand += " packages/" + installedMods[i] + "/oni/" + dir_itr->path().parent_path().filename() + '/' + dir_itr->path().filename();
556 }
[487]557 importCommand += " ../GameDataFolder/" + dir_itr->path().filename() + ".dat >> Install.log";
[325]558
[487]559
[325]560 setProgressBar( (int)(1000 * (float)(j-1) / (float)numberOfDats) ); //100% * dat we're on / total dats
[487]561 setStatusArea("Step " + lexical_cast<std::string>(j) + '/' + lexical_cast<std::string>(numberOfDats)+ ": Importing " + dir_itr->path().filename() + " ");
[325]562
563 system(importCommand.c_str());
[487]564 j++;
[325]565 }
566 }
567 catch ( const std::exception & ex )
568 {
[487]569 remove("Install.log");
570 ofstream logfile("Install.log");
571 logfile << "Warning, exception " << ex.what() << "!";
572 setStatusArea("Warning, exception " + (string)ex.what() + "!");
573 logfile.close();
[325]574 }
575 }
576 }
577 catch( const std::exception & ex ) {
[487]578 remove("Install.log");
579 ofstream logfile("Install.log");
580 logfile << "Warning, exception " << ex.what() << "!";
581 setStatusArea("Warning, exception " + (string)ex.what() + "!");
582 logfile.close();
[325]583 }
584 }
[487]585 else if(splitInstances == false){
[325]586 directory_iterator end_iter;
587
588 for ( directory_iterator dir_itr( vanilla_dir );
589 dir_itr != end_iter;
590 ++dir_itr )
591 {
592 if ( is_directory( dir_itr->status() ) )
593 {
594 numberOfDats++;
595 }
596 }
597
598 out << numberOfDats;
599 datString = out.str();
600
601 for ( directory_iterator dir_itr( vanilla_dir );
602 dir_itr != end_iter;
603 ++dir_itr )
604 {
605 try
606 {
607 if ( is_directory( dir_itr->status() ) )
608 {
[487]609 importCommand = strOniSplit + " " + strImportOption + " " + vanilla_dir.string() + dir_itr->path().filename() + " ";
610 for (unsigned int i = 0; i < installedMods.size(); ++i) {
[325]611 if (exists("packages/" + installedMods[i] + "/oni/" + dir_itr->path().filename() ))
612 importCommand += " packages/" + installedMods[i] + "/oni/" + dir_itr->path().filename();
613 }
[487]614 importCommand += " ../GameDataFolder/" + dir_itr->path().filename() + ".dat >> Install.log";
[325]615
616 setProgressBar( (int)(1000 * (float)(j-1) / (float)numberOfDats) ); //100% * dat we're on / total dats
[487]617 setStatusArea("Step " + lexical_cast<std::string>(j) + '/' + lexical_cast<std::string>(numberOfDats)+ ": Importing " + dir_itr->path().filename() + " ");
[325]618 system(importCommand.c_str());
619 j++;
620 }
621 }
622 catch ( const std::exception & ex )
623 {
[487]624 remove("Install.log");
625 ofstream logfile("Install.log");
626 logfile << "Warning, exception " << ex.what() << "!";
627 setStatusArea("Warning, exception " + (string)ex.what() + "!");
628 logfile.close();
[325]629 }
630 }
631 }
[487]632
633 vector<string> BSLfolders;
634 vector<string> skippedfolders;
635
636 ofstream BSLlog("BSL.log");
637 for ( directory_iterator dir_itr( "../GameDataFolder/IGMD/" ), end_itr;
638 dir_itr != end_itr;
639 ++dir_itr ) {
640 if( exists(dir_itr->path().string() + "/ignore.txt") ){
641 BSLfolders.push_back(dir_itr->path().filename());
642 skippedfolders.push_back(dir_itr->path().filename());
643 }
644 }
645
646 for (int i = installedMods.size() - 1; i >= 0; i--) { //Iterates through the installed mods (backwards :P)
647 for (unsigned int j = 0; j < globalPackages.size(); ++j) { //looking in the global packages
648 if (globalPackages[j].modStringName == installedMods[i]) { //for a mod that has BSL in it
649 if(!(globalPackages[j].hasAddon || globalPackages[j].hasBSL)) break; //skip non-BSL
650 if( exists( "packages/" + globalPackages[j].modStringName + "/BSL/" ) ) {
651 copyBSL("packages/" + globalPackages[j].modStringName + "/BSL", BSLfolders, globalPackages[j] );
652 BSLlog << "Copied " << globalPackages[j].modStringName << "!\n";
653 }
654 }
655 }
656 }
657
658 ModPackage emptyPackage;
659 emptyPackage.modStringName = "VanillaBSL";
660 emptyPackage.hasBSL = 1;
661 copyBSL("packages/VanillaBSL/IGMD", BSLfolders, emptyPackage);
662 BSLlog.close();
663
664 logfile << "Writing config file";
[325]665 writeInstalledMods(installedMods);
666 setProgressBar(1000);
[487]667
668 string finallyDone = "Done! You can now play Oni.";
669 setStatusArea(finallyDone);
670
671 ptime end_time(second_clock::local_time());
672 time_period total_time (start_time, end_time);
673 ofstream logfile2("Install.log", ios::app | ios::ate);
674 string outstring = (string)"\n\nGlobalization ended " + to_simple_string(end_time) + "\nThe process took ";// + (string)total_time.length();
675
676 logfile2 << "\nInstallation ended " << to_simple_string(end_time) << "\nThe process took " << total_time.length();
677 logfile2.close();
678
679 Sleep(1000);
[325]680 setProgressBar(0);
[487]681}
682 catch(exception & ex) {
683 remove("Install.log"); //why did we do this? :|
684 ofstream logfile("Install.log");
685 logfile << "Warning, exception " << ex.what() << "!";
686 setStatusArea("Warning, exception " + (string)ex.what() + "!");
687 logfile.close();
688 }
689 busy = 0;
690}
691
692void copyBSL(string copypath, vector<string>& BSLfolders, ModPackage pkg)
693{
694 ofstream BSLlog("BSL2.log", ios::app );
[325]695
[487]696 try {
697 for ( directory_iterator dir_itr( copypath ), end_itr;
698 dir_itr != end_itr;
699 ++dir_itr ) {
700
701 if ( is_directory( dir_itr->path() ) && dir_itr->path().string() != ".svn" ) {
702 BSLlog << "Testing " << dir_itr->path().string() << " HasBSL: " << pkg.hasBSL << " HasAddon: " << pkg.hasAddon << "\n";
703 int skip_folder = 0;
704
705 for(unsigned int k = 0; k < BSLfolders.size(); k++) {//iterate through already found BSL folders
706 BSLlog << "testing " << dir_itr->path().filename() << " vs " << BSLfolders[k] << "\n";
707 if(dir_itr->path().filename() == BSLfolders[k]) {
708 skip_folder = 1;
709 BSLlog << "skipping " << BSLfolders[k] << " in " << pkg.modStringName << "\n";
710 break;
711 }
712 }
713 if (!skip_folder && !exists("../GameDataFolder/IGMD/" + dir_itr->path().filename() + "/ignore.txt")) {
714 remove_all( "../GameDataFolder/IGMD/" + dir_itr->path().filename() );
715 Sleep(100);
716 create_directory( "../GameDataFolder/IGMD/" + dir_itr->path().filename());
717 BSLlog << "Copied " << dir_itr->path().string() << " in " << pkg.modStringName << "!\n";
718 for ( directory_iterator bsl_itr( dir_itr->path() );
719 bsl_itr != end_itr;
720 bsl_itr++ ) {
721 if ( bsl_itr->path().extension() == ".bsl" ) {
722 copy_file(bsl_itr->path(), "../GameDataFolder/IGMD/" + dir_itr->path().filename() + "/" + bsl_itr->path().filename());
723 }
724 }
725 BSLfolders.push_back( dir_itr->path().filename() ); //add back check for addon
726 BSLlog << "Pushing " << dir_itr->path().filename() << "\n" ;
727 }
728 }
729 }
730 }
731 catch ( const std::exception & ex )
732 {
733 setStatusArea("Warning, exception " + (string)ex.what() + "!");
734 while(1) Sleep(1000);
735 }
736 BSLlog.close();
737
[325]738}
739
[487]740
[325]741void writeInstalledMods(vector<string> installedMods)
[487]742{
[325]743 if ( exists( strInstallCfg ) )
744 {
745 remove( strInstallCfg );
746 }
747
748 ofstream file(strInstallCfg.c_str());
749
750 vector<string>list = installedMods;
751 vector<string>::iterator begin_iter = list.begin();
752 vector<string>::iterator end_iter = list.end();
753
754 sort( list.begin(), list.end() );
755
756 for( ; begin_iter != end_iter; ++begin_iter) {
757 file << *begin_iter << " ";
758 }
759
760 file.close();
761 file.clear();
762}
763
764vector<string> getInstallString(string Cfg)
765{
[487]766 vector<string> returnval;
[325]767 string line;
768 fstream file;
769
770 if (exists( Cfg ))
771 {
772 file.open(Cfg.c_str());
773 getline(file, line);
774 tokenize(line, returnval);
775 file.close();
776 file.clear();
777 sort(returnval.begin(), returnval.end());
778 }
779 else cout << "fail";
780
781 return returnval;
782}
783
[487]784/* GetUpdateStatus determines whether there is an update available. It is called once, *\
785| on launch, by AEInstallerApp::OnInit(), and not only passes back a #defined result |
786| code, but also oversees the setting of data in the global structures currentAE and |
787| updateAE, which tell the Installer all the version information it needs to know. |
788| ---Return Values--- |
789| UPDATE_LOG_READ_ERR -- A log file could not be opened |
790| UPDATE_INST_REPL_ERR -- The Installer self-updating process failed |
791| UPDATE_MNTH_REQD_ERR -- The update is a patch, and the monthly release it |
792| patches is not installed |
793| UPDATE_NO_UPD_AVAIL -- Either there isn't an update in place, or it's not |
794| newer than what's installed |
795| UPDATE_SIMP_AVAIL -- An update is available |
796| UPDATE_GLOB_AVAIL -- An update is available that requires re-globalization |
797| afterwards (because of some notable change in the AE) |
798| UPDATE_INST_AVAIL -- An update is available that first requires the |
799| Installer to be replaced (when the new Installer |
800| launches, this function will be called again but will |
801| return UPDATE_SIMP_AVAIL or UPDATE_GLOB_AVAIL) |
802\* UPDATE_CONT_UPD -- Currently unused */
803int GetUpdateStatus(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated)
804{
805 fstream currentAECfg, updateAECfg, updateLog;
806 string strInstaller = "Installer";
807 string strBeing = "being";
808 string strWas = "was"; // lol
809#ifdef WIN32
810 string strInstallerName = "AEInstaller.exe";
811#else
812 string strInstallerName = "Installer.app";
813#endif
814
815 // Try to get current AE's version info; if it doesn't exist, then the default version data for 2009-07 remains in place
816 if (exists("packages/Globalize/Install_Info.cfg"))
817 {
818 currentAECfg.open("packages/Globalize/Install_Info.cfg");
819 if (!currentAECfg.fail())
820 {
821 if (!ReadInstallInfoCfg(&currentAECfg, currentAE))
822 return UPDATE_LOG_READ_ERR;
823
824 currentAECfg.close();
825 currentAECfg.clear();
826 }
827 else
828 return UPDATE_LOG_READ_ERR;
829 }
830
831 // Is there an update folder, and is it a monthly release or a patch?
832 if (!exists("../updates/Edition"))
833 {
834 strEUFN = "Edition-patch";
835 if (!exists("../updates/Edition-patch"))
836 return UPDATE_NO_UPD_AVAIL;
837 }
838
839 // Unlike the current AE's version info, we *need* to find the update's version info or we won't continue
840 string updateCfgPath = ("../updates/" + strEUFN + "/install/packages/Globalize/Install_Info.cfg");
841 updateAECfg.open(updateCfgPath.c_str());
842 if (!updateAECfg.fail())
843 {
844 if (!ReadInstallInfoCfg(&updateAECfg, updateAE))
845 return UPDATE_LOG_READ_ERR;
846
847 updateAECfg.close();
848 updateAECfg.clear();
849 }
850 else
851 return UPDATE_LOG_READ_ERR;
852
853 // Now we check for an Installer update in progress
854 if (exists("Update.log"))
855 {
856 updateLog.open("Update.log");
857 if (!updateLog.fail())
858 {
859 vector<string> lines;
860 string line;
861 int num_lines = 0;
862 bool readingInstallerVersion = false, doneReadingFile = false;
863
864 while (!updateLog.eof() && !doneReadingFile)
865 {
866 getline(updateLog, line);
867 lines.push_back(line);
868 num_lines++;
869 vector<string> tokens;
870 vector<string>::iterator iter;
871 tokenize(line, tokens);
872 iter = tokens.begin();
873 if (!readingInstallerVersion && tokens.capacity() >= 4)
874 {
875 if (!strInstaller.compare(*iter))
876 {
877 if (!strBeing.compare(*++iter))
878 readingInstallerVersion = true;
879 else if (!strWas.compare(*iter))
880 *installerJustUpdated = true; // our third indirect return value after currentAE and updateAE
881 }
882 }
883 else if (readingInstallerVersion && tokens.capacity() >= 3)
884 {
885 readingInstallerVersion = false;
886 string installerVersion = INSTALLER_VERSION;
887 if (installerVersion.compare(*iter)) // then the shell script-powered replacement failed
888 return UPDATE_INST_REPL_ERR;
889 else
890 {
891 updateLog.close();
892 updateLog.clear();
893 Sleep(1000);
894 remove("Update.log");
895 ofstream newUpdateLog("Update.log");
896 if (!newUpdateLog.fail())
897 {
898 // Write over old log with updated information
899 ptime startTime(second_clock::local_time());
900 string strStartTime = to_simple_string(startTime);
901 string newUpdateLine = installerVersion + " on " + strStartTime;
902 for (int a = 0; a < lines.capacity() - 2; a++) // if there were even lines in the log before this at all
903 {
904 newUpdateLog << lines[a].c_str();
905 newUpdateLog << "\n";
906 }
907 newUpdateLog << "Installer was updated to:\n";
908 newUpdateLog << newUpdateLine.c_str();
909 *installerJustUpdated = true; // this value is indirectly returned to AEInstallerAp::OnInit()
910 doneReadingFile = true;
911 newUpdateLog.close();
912 newUpdateLog.clear();
913 //return UPDATE_CONT_UPD; // as noted above, we are not using this return value; in fact, we want...
914 // ...the code to continue running down through the Edition version check
915 }
916 else
917 return UPDATE_LOG_READ_ERR;
918 }
919 }
920 }
921 updateLog.close();
922 updateLog.clear();
923 }
924 else
925 return UPDATE_LOG_READ_ERR;
926 }
927
928 if (updateAE->AEVersion.compare(currentAE->AEVersion) >= 1) // is the release update newer than what's installed?
929 {
930 if (!strEUFN.compare("Edition-patch")) // if update is a patch...
931 {
932 if (currentAE->AEVersion.compare(updateAE->AEVersion.substr(0, updateAE->AEVersion.length() - 1))) // ...is it for a different month?
933 return UPDATE_MNTH_REQD_ERR;
934 }
935 string strNewInstallerPath = "../updates/" + strEUFN + "/install/" + strInstallerName;
936 string installerVersion = INSTALLER_VERSION;
937 if (updateAE->InstallerVersion.compare(installerVersion) >= 1)
938 {
939 if (exists(strNewInstallerPath))
940 return UPDATE_INST_AVAIL;
941 }
942 else if (updateAE->globalizationRequired)
943 return UPDATE_GLOB_AVAIL;
944 else
945 return UPDATE_SIMP_AVAIL;
946 }
947 else
948 return UPDATE_NO_UPD_AVAIL;
949
950 return UPDATE_NO_UPD_AVAIL;
951}
952
953bool ReadInstallInfoCfg(fstream *fileHandler, Install_info_cfg *info_cfg)
954{
955 vector<string> tokens;
956 vector<string>::iterator iter;
957 string line;
958 string strAEVersion = "AE_Version";
959 string strInstallerVersion = "Installer_Version";
960 string strDaodanVersion = "Daodan_Version";
961 string strOniSplitVersion = "OniSplit_Version";
962 string strGUIWinVersion = "GUI_Win_Version";
963 string strGUIMacVersion = "GUI_Mac_Version";
964 string strReglobalize = "Reglobalize";
965 string strDeleteList = "Delete_List";
966 string strArrow = "->";
967 string strDoubleSlash = "//";
968 string strYes = "Yes"; // this is getting silly
969
970 while (getline(*fileHandler, line))
971 {
[499]972 StripNewlines(&line);
[487]973 tokenize(line, tokens);
974 iter = tokens.begin();
975
976 if (tokens.size() >= 3)
977 {
978 if (!strAEVersion.compare(*iter))
979 {
980 if (!strArrow.compare(*++iter))
981 info_cfg->AEVersion = *++iter;
982 else
983 return false;
984 }
985 else if (!strInstallerVersion.compare(*iter))
986 {
987 if (!strArrow.compare(*++iter))
988 info_cfg->InstallerVersion = *++iter;
989 else
990 return false;
991 }
992 else if (!strDaodanVersion.compare(*iter))
993 {
994 if (!strArrow.compare(*++iter))
995 info_cfg->DaodanVersion = *++iter;
996 else
997 return false;
998 }
999 else if (!strOniSplitVersion.compare(*iter))
1000 {
1001 if (!strArrow.compare(*++iter))
1002 info_cfg->OniSplitVersion = *++iter;
1003 else
1004 return false;
1005 }
1006 else if (!strGUIWinVersion.compare(*iter))
1007 {
1008 if (!strArrow.compare(*++iter))
1009 info_cfg->WinGUIVersion = *++iter;
1010 else
1011 return false;
1012 }
1013 else if (!strGUIMacVersion.compare(*iter))
1014 {
1015 if (!strArrow.compare(*++iter))
1016 info_cfg->MacGUIVersion = *++iter;
1017 else
1018 return false;
1019 }
1020 else if (!strReglobalize.compare(*iter))
1021 {
1022 if (!strArrow.compare(*++iter))
1023 {
1024 if (!strYes.compare(*++iter))
1025 info_cfg->globalizationRequired = true;
1026 }
1027 else
1028 return false;
1029 }
1030 else if (!strDeleteList.compare(*iter))
1031 {
1032 // We need to perform a totally customized parsing process on this data
1033 if (!strArrow.compare(*++iter))
1034 {
1035 vector<string> tokens2;
1036 tokenize(line, tokens2, ","); // the paths on this line are comma-delimited, so we parse it again
1037 vector<string>::iterator iter2 = tokens2.begin();
1038 string finalPath = "";
1039 for (; iter2 != tokens2.end(); iter2++)
1040 {
1041 finalPath = finalPath + *iter2;
1042
1043 string::size_type loc = finalPath.find("->", 0); // the first word will have "Delete_List ->" at the front, so let's cut that off
1044 if (loc != string::npos)
1045 finalPath = finalPath.substr(loc + 3, finalPath.size());
1046
1047 // If a path has '//' in it, it must contain some optional comments that were at the end of the Delete_List line
1048 loc = finalPath.find("//", 0);
1049 if (loc != string::npos)
1050 finalPath = finalPath.substr(0, loc);
1051
1052 // Trim a single space if it exists at the start or finish; putting more than one space after a comma will break this
1053 if (finalPath.at(0) == ' ')
1054 finalPath = finalPath.substr(1, finalPath.size());
1055 if (finalPath.at(finalPath.size() - 1) == ' ')
1056 finalPath = finalPath.substr(0, finalPath.size() - 1);
1057
1058 // If the tokenized path ends with a '\', then we assume it was followed by a comma
1059 if (finalPath.at(finalPath.size() - 1) == '\\')
1060 {
1061 finalPath = finalPath.substr(0, finalPath.size() - 1); // clip the '\' off the end of the string now that it served its purpose...
1062 finalPath = finalPath + ","; // ...and add the actual comma back at the end
1063 }
1064 else // we can add the path to deleteList, and clear the path; otherwise it will be added to on the next iteration
1065 {
1066 if (StringIsLegalPathForDeletion(finalPath)) // ...and it's not violating any of our security rules as to what can be deleted...
1067 info_cfg->deleteList.push_back(finalPath); // ...then add it to our deleteList in memory
1068 finalPath.clear(); // clear the token we were building up before the next pass
1069 }
1070 }
1071 }
1072 else
1073 return false;
1074 }
1075 }
1076 tokens.clear();
1077 }
1078
1079 return true;
1080}
1081
1082// TODO: Fix security holes here
1083/* There is currently a security hole in this function; the first occurrence of a '.' not followed by a second '.' will prevent the function from
1084 noticing an actual occurrence of '..' later in the string; iow, it only looks after the first period it finds for a second period.
1085 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
1086 possibly be interpreted by the Boost file functions as "the current directory". Iow, both of these checks need to be iterative, not one-time.
1087 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 */
1088
1089/* This function serves as a barrier against the Installer deleting files it shouldn't. *\
1090| It tests for each of the following conditions in the path it is passed: |
1091| A. '..' as the whole path or '/..', '\..' anywhere in the path |
1092| Reason: Moving up from the parent directory, the Edition folder, would allow one |
1093| to delete anything on the hard drive, so all "parent path" references are illegal. |
1094| B. '/' at the beginning of the path |
1095| Reason: In Unix, this means the path starts from root level, as opposed to the |
1096| directory we will evaluate these paths as being relative to, which is Edition/. |
1097| C. '.' as the whole path |
1098| Reason: This would mean "the Edition folder", which is not allowed to be deleted. |
1099| D. 'GameDataFolder' at the end of the path |
1100| Reason: We don't allow the entire GDF to be deleted, only specific files in it. |
1101| E. '*' anywhere in the path |
1102| Reason: We don't want this interpreted as a wildcard; it's best to only delete |
1103*\ files by name. */
1104bool StringIsLegalPathForDeletion(string word)
1105{
1106 string::size_type loc1, loc2;
1107
1108 // Trim ending slashes in order to simplify the test
1109 // Note that we're only altering the local copy of the string here
1110 loc1 = word.find_last_of("\\", word.size());
1111 if (loc1 == word.size() - 1)
1112 word.resize(word.size() - 1);
1113 loc1 = word.find_last_of("/", word.size());
1114 if (loc1 == word.size() - 1)
1115 word.resize(word.size() - 1);
1116
1117 // Test B
1118 loc1 = word.find_first_of("\\", 0);
1119 if (loc1 == 0)
1120 return false; // path begins with a slash, meaning root level of HD in Unix, an illegal path
1121 loc1 = word.find_first_of("/", 0);
1122 if (loc1 == 0)
1123 return false; // path begins with a slash, meaning root level of HD in Unix, an illegal path
1124
1125 // Test E
1126 loc1 = word.find("*", 0);
1127 if (loc1 != string::npos) // if we found our character before reaching the end of the string
1128 return false; // path cannot contain the '*' character
1129
1130 // Tests A (part 1) and C
1131 loc1 = word.find(".", 0);
1132 if (loc1 != string::npos)
1133 {
1134 if (word.size() == 1)
1135 return false; // path cannot be simply '.', referring to Edition folder itself
1136 loc2 = word.find(".", loc1 + 1);
1137 if (loc2 == loc1 + 1) // make sure this second period comes after the first one
1138 if (word.size() == 2)
1139 return false; // not allowed to reference a parent directory
1140 }
1141
1142 // Test A (part 2)
1143 loc1 = word.find("/..", 0);
1144 if (loc1 != string::npos)
1145 return false; // not allowed to reference a parent directory
1146 loc1 = word.find("\\..", 0);
1147 if (loc1 != string::npos)
1148 return false; // not allowed to reference a parent directory
1149
1150 // Test D
1151 loc1 = word.find("GameDataFolder", 0);
1152 if (loc1 == word.size() - 14) // if "GameDataFolder" is the last 14 characters of the string...
1153 return false; // not allowed to delete the GDF
1154
1155 return true;
1156}
1157
1158bool ProcessInstallerUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE)
1159{
1160 ofstream file;
1161 string shellScript;
1162
1163 ptime startTime(second_clock::local_time());
1164 string strStartTime = to_simple_string(startTime);
1165 string progressMsg = "Installer being updated to:\n" +
1166 updateAE->InstallerVersion + " on " + strStartTime;
1167 file.open("Update.log");
1168 if (!file.fail())
1169 file << progressMsg.c_str();
1170 file.close();
1171 file.clear();
1172
1173 string popenCommand = "../updates/" + strEUFN + "/install/";
1174#ifdef WIN32
1175 // TODO: Fill in Windows equivalent of code below :-3
[496]1176 popenCommand = "replace_installer.bat";
[487]1177#else
1178 // We can't just use '~' to mean "the home directory" because we need to check the path in C...
1179 // ...so we actually get the current user's shortname and manually construct the path to home
1180 FILE *fUserName = NULL;
1181 char chrUserName[32];
1182 fUserName = popen("whoami", "r");
1183 fgets(chrUserName, sizeof(chrUserName), fUserName);
1184 pclose(fUserName);
1185 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1186 int endOfName = strUserName.find("\n", 0);
1187 string pathToTrash = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1188 tm tmStartTime = to_tm(startTime);
1189 pathToTrash = pathToTrash + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1190 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec); // lol
1191 create_directory(pathToTrash);
1192 // The script takes as a parameter the path the old Installer should go to, in quotes
1193 popenCommand = "bash " + popenCommand + "replace_installer.sh " + pathToTrash + "/Installer.app";
1194
1195#endif
1196 file.close();
1197 file.clear();
[496]1198#ifdef WIN32
1199 system(popenCommand.c_str());
1200#else
[487]1201 popen(popenCommand.c_str(), "r");
[496]1202#endif
[487]1203 return true; // returning 'true' tells the Installer to quit itself ASAP so it can be replaced by the process that is now running
1204}
1205
1206bool ProcessAEUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated)
1207{
1208 fstream file;
1209 string line;
1210 vector<string> tokens, updateStarted;
1211 string strInstaller = "Installer";
1212 string strWas = "was";
1213 string strPathToEUFN = ("../updates/" + strEUFN + "/"); // strEUFN is set by GetUpdateStatus()
1214 string strPathToEUFNInstall = ("../updates/" + strEUFN + "/install/");
1215 string strPathToEUFNPackages = ("../updates/" + strEUFN + "/install/packages/");
1216 string strPathToPackages = "packages/";
1217 string strGlobalize = "Globalize/";
1218 string strOniSplit = "OniSplit.exe";
1219 string strDaodan = "binkw32.dll";
1220 string strWinGUI = "onisplit_gui.exe";
1221 string strWinGUILang = "ospgui_lang.ini";
1222 string strMacGUI = "AETools.app";
1223#ifdef WIN32
1224 string strOniApp = "Oni.exe";
1225#else
1226 string strOniApp = "Oni.app";
1227#endif
1228 bool needNewTrashDir = false;
1229 bool readingVerAndDate = false;
1230
1231 // TODO: Fill in Windows equivalent of code below
1232#ifdef WIN32
[496]1233 string strTrashDir = "Trash\\";
[487]1234#else
1235 FILE *fUserName = NULL;
1236 char chrUserName[32];
1237 fUserName = popen("whoami", "r");
1238 fgets(chrUserName, sizeof(chrUserName), fUserName);
1239 pclose(fUserName);
1240 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1241 int endOfName = strUserName.find("\n", 0);
1242 string strTrashDir = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1243#endif
1244
1245 // Write to log that we are beginning the update process
1246 ptime startTime(second_clock::local_time());
1247 string strStartTime = to_simple_string(startTime);
1248 string progressMsg = "\nEdition being updated to:\n" +
1249 updateAE->AEVersion + " on " + strStartTime;
1250 file.open("Update.log");
1251 if (!file.fail())
1252 file << progressMsg.c_str();
1253
1254 if (*installerJustUpdated) // then we want to know what folder in the Trash the Installer was placed in...
1255 {
1256 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
1257 {
1258 getline(file, line);
1259 tokenize(line, tokens);
1260
1261 if (tokens.capacity() >= 4)
1262 if (!strInstaller.compare(tokens[0]))
1263 if (!strWas.compare(tokens[1]))
1264 readingVerAndDate = true;
1265 if (readingVerAndDate && tokens.capacity() >= 3)
1266 tokenize(tokens[2], updateStarted, "-");
1267 }
1268 if (updateStarted.capacity() < 3)
1269 needNewTrashDir = true;
1270 else
1271 {
1272 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "-" +
1273 updateStarted[0] + "-" + updateStarted[1] + "-" + updateStarted[2] + "/";
1274 if (!exists(strTrashDir))
1275 needNewTrashDir = true;
1276 }
1277 }
[496]1278#ifndef WIN32
[487]1279 if (!*installerJustUpdated || needNewTrashDir) // prepare a new directory for deleted files to go to
1280 {
1281 tm tmStartTime = to_tm(startTime);
1282 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1283 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec) + "/";
1284 create_directory(strTrashDir);
1285 }
[496]1286#endif
[487]1287 file.close();
1288 file.clear();
1289
1290 // Special code to replace our special files -- the Oni app, OniSplit, the Daodan DLL, and the GUI for OniSplit
1291 if (exists(strPathToEUFN + strOniApp))
1292 {
1293 if (exists(strOniApp))
1294 rename((path)strOniApp, (path)(strTrashDir + strOniApp));
1295 rename((path)(strPathToEUFN + strOniApp), (path)strOniApp);
1296 }
1297 if (updateAE->OniSplitVersion.compare(currentAE->OniSplitVersion) >= 1)
1298 {
1299 if (exists(strPathToEUFNInstall + strOniSplit))
1300 {
1301 if (exists(strOniSplit))
1302 rename((path)strOniSplit, (path)(strTrashDir + strOniSplit));
1303 rename((path)(strPathToEUFNInstall + strOniSplit), (path)strOniSplit);
1304 }
1305 }
1306#ifdef WIN32
1307 if (updateAE->DaodanVersion.compare(currentAE->DaodanVersion) >= 1)
1308 {
1309 if (exists(strPathToEUFN + strDaodan))
1310 {
1311 if (exists(("../" + strDaodan)))
1312 rename((path)("../" + strDaodan), (path)(strTrashDir + strDaodan));
1313 rename((path)(strPathToEUFN + strDaodan), (path)("../" + strDaodan));
1314 }
1315 }
1316 if (updateAE->WinGUIVersion.compare(currentAE->WinGUIVersion) >= 1)
1317 {
1318 if (exists(strPathToEUFNInstall + strWinGUI))
1319 {
1320 if (exists((path)strWinGUI))
[489]1321 rename((path)strWinGUI, (path)(strTrashDir + strWinGUI));
[487]1322 if (exists(strWinGUILang))
[489]1323 rename((path)strWinGUILang, (path)(strTrashDir + strWinGUILang));
[487]1324 rename((path)(strPathToEUFNInstall + strWinGUI), (path)strWinGUI);
1325 rename((path)(strPathToEUFNInstall + strWinGUILang), (path)strWinGUILang);
1326 }
1327 }
1328#else
1329 if (updateAE->MacGUIVersion.compare(currentAE->MacGUIVersion) >= 1)
1330 {
1331 if (exists(strPathToEUFN + strMacGUI))
1332 {
1333 if (exists(("../" + strMacGUI)))
1334 rename((path)("../" + strMacGUI), (path)(strTrashDir + strMacGUI));
1335 rename((path)(strPathToEUFN + strMacGUI), (path)("../" + strMacGUI));
1336 }
1337 }
1338#endif
1339
1340 // Now we trash whatever's in DeleteList; this allows us to clear out obsolete files in the previous AE install
1341 // Before moving a file to the Trash, we need to make sure each of the file's parent paths exists in the Trash...
1342 // ...so we iterate through the hierarchy of the file path, checking for each one and creating it if necessary
1343 for (vector<string>::iterator iter = updateAE->deleteList.begin(); iter != updateAE->deleteList.end(); iter++)
1344 {
1345 string thePath = *iter;
1346 if (exists((path)("../" + thePath)))
1347 {
1348 string aParentPath;
1349 string::size_type curPos = thePath.find("/", 0);
1350 if (curPos != string::npos)
1351 aParentPath = thePath.substr(0, curPos);
1352 string::size_type lastPos = curPos;
1353 while (curPos != string::npos && curPos < thePath.size())
1354 {
1355 aParentPath = aParentPath + thePath.substr(lastPos, curPos - lastPos);
1356 if (!exists(strTrashDir + aParentPath))
1357 create_directory(strTrashDir + aParentPath);
1358 lastPos = curPos + 1;
1359 curPos = thePath.find("/", lastPos);
1360 aParentPath = aParentPath + "/";
1361 }
[496]1362#ifndef WIN32
[487]1363 rename((path)("../" + thePath), (path)(strTrashDir + thePath));
[496]1364#else
1365 remove((path)("../" + thePath));
1366#endif
[487]1367 }
1368 }
1369
1370 // Now we crawl the update's package folders for newer versions and move them over, trashing ones that are already present
1371 vector<ModPackage> updatePackages, currentPackages;
1372 bool matchFound;
1373 updatePackages.reserve(256);
1374 currentPackages.reserve(256);
1375
1376 currentPackages = getPackages();
1377 updatePackages = getPackages(strPathToEUFNPackages);
1378
1379 for (vector<ModPackage>::iterator iter1 = updatePackages.begin(); iter1 != updatePackages.end(); iter1++)
1380 {
1381 matchFound = false;
1382 for (vector<ModPackage>::iterator iter2 = currentPackages.begin(); iter2 != currentPackages.end(); iter2++)
1383 {
1384 if (!iter1->modStringName.compare(iter2->modStringName))
1385 {
1386 matchFound = true;
1387 if (iter1->modStringVersion > iter2->modStringVersion)
1388 {
[496]1389#ifndef WIN32
[487]1390 rename((path)(strPathToPackages + iter2->modStringName), (path)(strTrashDir + iter2->modStringName));
[496]1391#else
1392 remove((path)(strPathToPackages + iter2->modStringName));
1393#endif
[487]1394 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1395 }
1396 }
1397 }
1398 if (!matchFound) // then there's no old package in the way, so just move in the one from the update
1399 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1400 }
1401
1402 // 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...
1403 // ...and folders which do not exist in the current AE will be created there
1404 vector<string> foldersToMake, filesToMove;
1405 string thePath;
1406 for (recursive_directory_iterator dir_itr(strPathToEUFNPackages + strGlobalize), end_itr; dir_itr != end_itr; ++dir_itr)
1407 {
1408 thePath = dir_itr->path().string();
1409 MakePathLocalToGlobalize(&thePath);
1410 if (is_regular_file(dir_itr->status()))
1411 {
1412 if (dir_itr->filename().at(0) != '.') // skip over dot-files, which are invisible in Unix
1413 filesToMove.push_back(thePath);
1414 }
1415 else if (is_directory(dir_itr->status()))
1416 {
1417 if (!exists(strPathToPackages + strGlobalize + thePath))
1418 foldersToMake.push_back(thePath);
1419 }
1420 }
1421 // Sort the foldersToMake strings by length, which is a fast solution to the problem of "How do we make sure we create folder 'parent/'...
1422 // ...before folder 'parent/child/'"?
1423 sort(foldersToMake.begin(), foldersToMake.end(), SortBySize); // SortBySize is a custom comparison function found later in this source file
1424 // First make the folders that don't exist in the current AE, so all the files have a place to go
1425 for (vector<string>::iterator iter = foldersToMake.begin(); iter != foldersToMake.end(); iter++)
1426 {
1427 create_directory(strPathToPackages + strGlobalize + *iter);
1428 }
1429 for (vector<string>::iterator iter = filesToMove.begin(); iter != filesToMove.end(); iter++)
1430 {
1431 if (exists(strPathToPackages + strGlobalize + *iter))
1432 rename((path)(strPathToPackages + strGlobalize + *iter), (path)(strTrashDir + *iter));
1433 rename((path)(strPathToEUFNPackages + strGlobalize + *iter), (path)(strPathToPackages + strGlobalize + *iter));
1434 }
1435
1436 // Clean up after ourselves, trashing any packages or programs in the update package that are not newer than the current AE
1437 create_directory(strTrashDir + "Unneeded update files");
1438 rename((path)strPathToEUFN, (path)(strTrashDir + "Unneeded update files/" + strEUFN));
1439
1440 // Write to log that we are finished with update
1441 ptime end_time(second_clock::local_time());
1442 string progressMsg2 = "Edition was updated to:\n" +
1443 updateAE->AEVersion + " on " + to_simple_string(end_time);
1444
1445 file.open("Update.log");
1446
1447 if(!file.fail())
1448 file.write(progressMsg2.c_str(), sizeof(progressMsg2.c_str()));
1449
1450 file.close();
1451 file.clear();
1452
1453 if (updateAE->globalizationRequired)
1454 CheckForGlobalization(true); // the 'true' value forces re-globalization
1455
1456 globalPackages = getPackages(); // refresh the list in memory
1457
1458 // TODO: Refresh the packages list in the window
1459
1460 return true;
1461}
1462
1463/* MakePathLocalToGlobalize is a function used once by ProcessAEUpdate() that takes a file in an \
1464| update's Globalize folder and changes its path, originally relative to the Installer, to be |
1465| relative to the structure of the Globalize folder; this makes it easier to have the Installer |
1466\ move said file from an update's Globalize folder to the current Globalize folder. */
1467void MakePathLocalToGlobalize(string *installerBasedPath)
1468{
1469 int deleteToHere = 0;
1470 deleteToHere = installerBasedPath->find("Globalize/");
1471 if (deleteToHere != 0)
1472 {
1473 deleteToHere += strlen("Globalize/");
1474 installerBasedPath->erase(0, deleteToHere);
1475 }
1476}
1477
1478/* SortBySize is a custom comparison function that we call when using the C++ sort() function in \
1479\ ProcessAEUpdate() on some strings that we want to sort by length. */
1480bool SortBySize(string a, string b)
1481{
1482 return (a.size() < b.size());
1483}
1484
[325]1485//stolen token function...
1486void tokenize(const string& str, vector<string>& tokens, const string& delimiters)
1487{
1488 // Skip delimiters at beginning.
1489 string::size_type lastPos = str.find_first_not_of(delimiters, 0);
1490 // Find first "non-delimiter".
1491 string::size_type pos = str.find_first_of(delimiters, lastPos);
1492
1493 while (string::npos != pos || string::npos != lastPos)
1494 {
1495 // Found a token, add it to the vector.
1496 tokens.push_back(str.substr(lastPos, pos - lastPos));
1497 // Skip delimiters. Note the "not_of"
1498 lastPos = str.find_first_not_of(delimiters, pos);
1499 // Find next "non-delimiter"
1500 pos = str.find_first_of(delimiters, lastPos);
1501 }
1502}
1503
[499]1504/* StripNewlines() gets rids of any linebreaks that come from text returned by getline(); \
1505| getline() should be stripping those out, but Windows CR/LF files seem to be sneaking |
1506\ some extra return characters into strings in the ReadInstallInfoCfg() function. */
1507void StripNewlines(string *theLine)
1508{
1509 int deleteFromHere = 0;
1510 deleteFromHere = theLine->find("\r");
1511 if (deleteFromHere > 0)
1512 theLine->erase(deleteFromHere, theLine->size());
1513}
1514
[325]1515void clearOldDats(void) {
1516 directory_iterator end_iter_gdf;
1517 for ( directory_iterator dir_itr_gdf( "../GameDataFolder" );
1518 dir_itr_gdf != end_iter_gdf;
1519 ++dir_itr_gdf )
1520 {
1521 if ( dir_itr_gdf->path().extension() == ".dat" || dir_itr_gdf->path().extension() == ".raw" || dir_itr_gdf->path().extension() == ".sep" ) {
1522 remove( dir_itr_gdf->path() );
1523 }
1524
1525 }
1526
[487]1527}
1528
1529// this function copies files and directories. If copying a
1530// directory to a directory, it copies recursively.
1531
1532//pardon the mess, I did this at midnight, and had to fix a bug
1533void copy( const path & from_ph,
1534 const path & to_ph )
1535{
1536 cout << to_ph.string() << "\n";
1537 // Make sure that the destination, if it exists, is a directory
1538 if((exists(to_ph) && !is_directory(to_ph)) || (!exists(from_ph))) cout << "error";
1539 if(!is_directory(from_ph))
1540 {
1541
1542 if(exists(to_ph))
1543 {
1544 copy_file(from_ph,to_ph/from_ph.filename());
1545 }
1546 else
1547 {
1548 try{
1549
1550 copy_file(from_ph,to_ph);
1551 }
1552 catch (exception ex){
1553 cout << from_ph.string() << " to " << to_ph.string() << "\n";
1554 }
1555 }
1556
1557 }
1558 else if(from_ph.filename() != ".svn")
1559 {
1560 path destination;
1561 if(!exists(to_ph))
1562 {
1563 destination=to_ph;
1564 }
1565 else
1566 {
1567 destination=to_ph/from_ph.filename();
1568 }
1569
1570 for(directory_iterator i(from_ph); i!=directory_iterator(); ++i)
1571 {
1572 //the idiot who coded this in the first place (not me)
1573 //forgot to make a new directory. Exception city. x_x
1574 create_directory(destination);
1575 copy(*i,destination/i->filename());
1576 }
1577 }
1578}
1579
1580void copy_directory( const path &from_dir_ph,
1581 const path &to_dir_ph)
1582{
1583 if(!exists(from_dir_ph) || !is_directory(from_dir_ph)
1584 || exists(to_dir_ph))
1585 cout << !exists(from_dir_ph) << " " << !is_directory(from_dir_ph)
1586 << " " << exists(to_dir_ph);
1587
1588# ifdef BOOST_POSIX
1589 struct stat from_stat;
1590 if ( (::stat( from_dir_ph.string().c_str(), &from_stat ) != 0)
1591 || ::mkdir(to_dir_ph.native_directory_string().c_str(),
1592 from_stat.st_mode)!=0)
1593# endif
1594 }
1595
1596string escapePath(string input)
1597{
1598 string output;
1599 string escape_me = "& ;()|<>\"'\\#*?$";
1600 for (unsigned int i = 0; i < input.size(); i++)
1601 {
1602 for (unsigned int j = 0; j < escape_me.size(); j++)
1603 if (input[i] == escape_me[j])
1604 output += '\\';
1605 output += input[i];
1606 }
1607 return output;
1608}
1609
1610Install_info_cfg::Install_info_cfg()
1611{
1612 AEVersion = "2009-07b";
1613 InstallerVersion = "1.0.1";
1614 DaodanVersion = "1.0";
1615 OniSplitVersion = "0.9.38.0";
1616 WinGUIVersion = "0";
1617 MacGUIVersion = "0";
1618 patch = false;
1619 globalizationRequired = false;
1620 deleteList.reserve(255);
1621}
1622
1623ModPackage::ModPackage()
1624{
1625 isInstalled = true; // replace with function
1626 name = "";
1627 modStringName = "";
1628 modStringVersion = 0;
[502]1629 platform = "Both";
[487]1630 hasOnis = false;
1631 hasDeltas = false;
1632 hasBSL = false;
1633 hasAddon = false;
1634 hasDats = false;
1635 category = "";
1636 creator = "";
1637 isEngine = false;
1638 readme = "";
1639 globalNeeded = true;
1640}
[489]1641#ifndef WIN32
[487]1642void Sleep(int ms)
1643{
1644 sleep(ms / 1000);
1645}
[489]1646#endif
[487]1647#ifdef WIN32
1648
1649void RedirectIOToConsole()
1650{
1651 int hConHandle;
1652 long lStdHandle;
1653 CONSOLE_SCREEN_BUFFER_INFO coninfo;
1654 FILE *fp;
1655
1656 // allocate a console for this app
1657 AllocConsole();
1658
1659 // set the screen buffer to be big enough to let us scroll text
1660 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
1661 &coninfo);
1662 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
1663 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
1664 coninfo.dwSize);
1665
1666 // redirect unbuffered STDOUT to the console
1667 lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
1668 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1669 fp = _fdopen( hConHandle, "w" );
1670 *stdout = *fp;
1671 setvbuf( stdout, NULL, _IONBF, 0 );
1672
1673 // redirect unbuffered STDIN to the console
1674 lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
1675 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1676 fp = _fdopen( hConHandle, "r" );
1677 *stdin = *fp;
1678 setvbuf( stdin, NULL, _IONBF, 0 );
1679
1680 // redirect unbuffered STDERR to the console
1681 lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
1682 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1683 fp = _fdopen( hConHandle, "w" );
1684 *stderr = *fp;
1685 setvbuf( stderr, NULL, _IONBF, 0 );
1686
1687 // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
1688 // point to console as well
1689 ios::sync_with_stdio();
1690}
1691#endif
Note: See TracBrowser for help on using the repository browser.