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

Last change on this file since 501 was 500, checked in by iritscen, 15 years ago

Changed 'Installer replacement failed' message so user can choose not to quit.
Fixed BSL mod package-detecting bug and other dubious flag-detecting code.
Added check for required Installer version when scanning package info files.

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