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

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

I don't fully understand the CR/LF bug, but this fixes it *shrug*.

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