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

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

Adding update feature; moving/neatening some code; adding globals.h

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