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

Last change on this file since 494 was 489, checked in by gumby, 15 years ago

Fixes for WIN32

File size: 60.8 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 tokenize(line, tokens);
943 iter = tokens.begin();
944
945 if (tokens.size() >= 3)
946 {
947 if (!strAEVersion.compare(*iter))
948 {
949 if (!strArrow.compare(*++iter))
950 info_cfg->AEVersion = *++iter;
951 else
952 return false;
953 }
954 else if (!strInstallerVersion.compare(*iter))
955 {
956 if (!strArrow.compare(*++iter))
957 info_cfg->InstallerVersion = *++iter;
958 else
959 return false;
960 }
961 else if (!strDaodanVersion.compare(*iter))
962 {
963 if (!strArrow.compare(*++iter))
964 info_cfg->DaodanVersion = *++iter;
965 else
966 return false;
967 }
968 else if (!strOniSplitVersion.compare(*iter))
969 {
970 if (!strArrow.compare(*++iter))
971 info_cfg->OniSplitVersion = *++iter;
972 else
973 return false;
974 }
975 else if (!strGUIWinVersion.compare(*iter))
976 {
977 if (!strArrow.compare(*++iter))
978 info_cfg->WinGUIVersion = *++iter;
979 else
980 return false;
981 }
982 else if (!strGUIMacVersion.compare(*iter))
983 {
984 if (!strArrow.compare(*++iter))
985 info_cfg->MacGUIVersion = *++iter;
986 else
987 return false;
988 }
989 else if (!strReglobalize.compare(*iter))
990 {
991 if (!strArrow.compare(*++iter))
992 {
993 if (!strYes.compare(*++iter))
994 info_cfg->globalizationRequired = true;
995 }
996 else
997 return false;
998 }
999 else if (!strDeleteList.compare(*iter))
1000 {
1001 // We need to perform a totally customized parsing process on this data
1002 if (!strArrow.compare(*++iter))
1003 {
1004 vector<string> tokens2;
1005 tokenize(line, tokens2, ","); // the paths on this line are comma-delimited, so we parse it again
1006 vector<string>::iterator iter2 = tokens2.begin();
1007 string finalPath = "";
1008 for (; iter2 != tokens2.end(); iter2++)
1009 {
1010 finalPath = finalPath + *iter2;
1011
1012 string::size_type loc = finalPath.find("->", 0); // the first word will have "Delete_List ->" at the front, so let's cut that off
1013 if (loc != string::npos)
1014 finalPath = finalPath.substr(loc + 3, finalPath.size());
1015
1016 // If a path has '//' in it, it must contain some optional comments that were at the end of the Delete_List line
1017 loc = finalPath.find("//", 0);
1018 if (loc != string::npos)
1019 finalPath = finalPath.substr(0, loc);
1020
1021 // Trim a single space if it exists at the start or finish; putting more than one space after a comma will break this
1022 if (finalPath.at(0) == ' ')
1023 finalPath = finalPath.substr(1, finalPath.size());
1024 if (finalPath.at(finalPath.size() - 1) == ' ')
1025 finalPath = finalPath.substr(0, finalPath.size() - 1);
1026
1027 // If the tokenized path ends with a '\', then we assume it was followed by a comma
1028 if (finalPath.at(finalPath.size() - 1) == '\\')
1029 {
1030 finalPath = finalPath.substr(0, finalPath.size() - 1); // clip the '\' off the end of the string now that it served its purpose...
1031 finalPath = finalPath + ","; // ...and add the actual comma back at the end
1032 }
1033 else // we can add the path to deleteList, and clear the path; otherwise it will be added to on the next iteration
1034 {
1035 if (StringIsLegalPathForDeletion(finalPath)) // ...and it's not violating any of our security rules as to what can be deleted...
1036 info_cfg->deleteList.push_back(finalPath); // ...then add it to our deleteList in memory
1037 finalPath.clear(); // clear the token we were building up before the next pass
1038 }
1039 }
1040 }
1041 else
1042 return false;
1043 }
1044 }
1045 tokens.clear();
1046 }
1047
1048 return true;
1049}
1050
1051// TODO: Fix security holes here
1052/* There is currently a security hole in this function; the first occurrence of a '.' not followed by a second '.' will prevent the function from
1053 noticing an actual occurrence of '..' later in the string; iow, it only looks after the first period it finds for a second period.
1054 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
1055 possibly be interpreted by the Boost file functions as "the current directory". Iow, both of these checks need to be iterative, not one-time.
1056 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 */
1057
1058/* This function serves as a barrier against the Installer deleting files it shouldn't. *\
1059| It tests for each of the following conditions in the path it is passed: |
1060| A. '..' as the whole path or '/..', '\..' anywhere in the path |
1061| Reason: Moving up from the parent directory, the Edition folder, would allow one |
1062| to delete anything on the hard drive, so all "parent path" references are illegal. |
1063| B. '/' at the beginning of the path |
1064| Reason: In Unix, this means the path starts from root level, as opposed to the |
1065| directory we will evaluate these paths as being relative to, which is Edition/. |
1066| C. '.' as the whole path |
1067| Reason: This would mean "the Edition folder", which is not allowed to be deleted. |
1068| D. 'GameDataFolder' at the end of the path |
1069| Reason: We don't allow the entire GDF to be deleted, only specific files in it. |
1070| E. '*' anywhere in the path |
1071| Reason: We don't want this interpreted as a wildcard; it's best to only delete |
1072*\ files by name. */
1073bool StringIsLegalPathForDeletion(string word)
1074{
1075 string::size_type loc1, loc2;
1076
1077 // Trim ending slashes in order to simplify the test
1078 // Note that we're only altering the local copy of the string here
1079 loc1 = word.find_last_of("\\", word.size());
1080 if (loc1 == word.size() - 1)
1081 word.resize(word.size() - 1);
1082 loc1 = word.find_last_of("/", word.size());
1083 if (loc1 == word.size() - 1)
1084 word.resize(word.size() - 1);
1085
1086 // Test B
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 loc1 = word.find_first_of("/", 0);
1091 if (loc1 == 0)
1092 return false; // path begins with a slash, meaning root level of HD in Unix, an illegal path
1093
1094 // Test E
1095 loc1 = word.find("*", 0);
1096 if (loc1 != string::npos) // if we found our character before reaching the end of the string
1097 return false; // path cannot contain the '*' character
1098
1099 // Tests A (part 1) and C
1100 loc1 = word.find(".", 0);
1101 if (loc1 != string::npos)
1102 {
1103 if (word.size() == 1)
1104 return false; // path cannot be simply '.', referring to Edition folder itself
1105 loc2 = word.find(".", loc1 + 1);
1106 if (loc2 == loc1 + 1) // make sure this second period comes after the first one
1107 if (word.size() == 2)
1108 return false; // not allowed to reference a parent directory
1109 }
1110
1111 // Test A (part 2)
1112 loc1 = word.find("/..", 0);
1113 if (loc1 != string::npos)
1114 return false; // not allowed to reference a parent directory
1115 loc1 = word.find("\\..", 0);
1116 if (loc1 != string::npos)
1117 return false; // not allowed to reference a parent directory
1118
1119 // Test D
1120 loc1 = word.find("GameDataFolder", 0);
1121 if (loc1 == word.size() - 14) // if "GameDataFolder" is the last 14 characters of the string...
1122 return false; // not allowed to delete the GDF
1123
1124 return true;
1125}
1126
1127bool ProcessInstallerUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE)
1128{
1129 ofstream file;
1130 string shellScript;
1131
1132 ptime startTime(second_clock::local_time());
1133 string strStartTime = to_simple_string(startTime);
1134 string progressMsg = "Installer being updated to:\n" +
1135 updateAE->InstallerVersion + " on " + strStartTime;
1136 file.open("Update.log");
1137 if (!file.fail())
1138 file << progressMsg.c_str();
1139 file.close();
1140 file.clear();
1141
1142 string popenCommand = "../updates/" + strEUFN + "/install/";
1143#ifdef WIN32
1144 // TODO: Fill in Windows equivalent of code below :-3
1145#else
1146 // We can't just use '~' to mean "the home directory" because we need to check the path in C...
1147 // ...so we actually get the current user's shortname and manually construct the path to home
1148 FILE *fUserName = NULL;
1149 char chrUserName[32];
1150 fUserName = popen("whoami", "r");
1151 fgets(chrUserName, sizeof(chrUserName), fUserName);
1152 pclose(fUserName);
1153 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1154 int endOfName = strUserName.find("\n", 0);
1155 string pathToTrash = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1156 tm tmStartTime = to_tm(startTime);
1157 pathToTrash = pathToTrash + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1158 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec); // lol
1159 create_directory(pathToTrash);
1160 // The script takes as a parameter the path the old Installer should go to, in quotes
1161 popenCommand = "bash " + popenCommand + "replace_installer.sh " + pathToTrash + "/Installer.app";
1162
1163#endif
1164 file.close();
1165 file.clear();
1166 popen(popenCommand.c_str(), "r");
1167
1168 return true; // returning 'true' tells the Installer to quit itself ASAP so it can be replaced by the process that is now running
1169}
1170
1171bool ProcessAEUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated)
1172{
1173 fstream file;
1174 string line;
1175 vector<string> tokens, updateStarted;
1176 string strInstaller = "Installer";
1177 string strWas = "was";
1178 string strPathToEUFN = ("../updates/" + strEUFN + "/"); // strEUFN is set by GetUpdateStatus()
1179 string strPathToEUFNInstall = ("../updates/" + strEUFN + "/install/");
1180 string strPathToEUFNPackages = ("../updates/" + strEUFN + "/install/packages/");
1181 string strPathToPackages = "packages/";
1182 string strGlobalize = "Globalize/";
1183 string strOniSplit = "OniSplit.exe";
1184 string strDaodan = "binkw32.dll";
1185 string strWinGUI = "onisplit_gui.exe";
1186 string strWinGUILang = "ospgui_lang.ini";
1187 string strMacGUI = "AETools.app";
1188#ifdef WIN32
1189 string strOniApp = "Oni.exe";
1190#else
1191 string strOniApp = "Oni.app";
1192#endif
1193 bool needNewTrashDir = false;
1194 bool readingVerAndDate = false;
1195
1196 // TODO: Fill in Windows equivalent of code below
1197#ifdef WIN32
1198 string strTrashDir = "%RECYCLE%";
1199#else
1200 FILE *fUserName = NULL;
1201 char chrUserName[32];
1202 fUserName = popen("whoami", "r");
1203 fgets(chrUserName, sizeof(chrUserName), fUserName);
1204 pclose(fUserName);
1205 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1206 int endOfName = strUserName.find("\n", 0);
1207 string strTrashDir = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1208#endif
1209
1210 // Write to log that we are beginning the update process
1211 ptime startTime(second_clock::local_time());
1212 string strStartTime = to_simple_string(startTime);
1213 string progressMsg = "\nEdition being updated to:\n" +
1214 updateAE->AEVersion + " on " + strStartTime;
1215 file.open("Update.log");
1216 if (!file.fail())
1217 file << progressMsg.c_str();
1218
1219 if (*installerJustUpdated) // then we want to know what folder in the Trash the Installer was placed in...
1220 {
1221 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
1222 {
1223 getline(file, line);
1224 tokenize(line, tokens);
1225
1226 if (tokens.capacity() >= 4)
1227 if (!strInstaller.compare(tokens[0]))
1228 if (!strWas.compare(tokens[1]))
1229 readingVerAndDate = true;
1230 if (readingVerAndDate && tokens.capacity() >= 3)
1231 tokenize(tokens[2], updateStarted, "-");
1232 }
1233 if (updateStarted.capacity() < 3)
1234 needNewTrashDir = true;
1235 else
1236 {
1237 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "-" +
1238 updateStarted[0] + "-" + updateStarted[1] + "-" + updateStarted[2] + "/";
1239 if (!exists(strTrashDir))
1240 needNewTrashDir = true;
1241 }
1242 }
1243
1244 if (!*installerJustUpdated || needNewTrashDir) // prepare a new directory for deleted files to go to
1245 {
1246 tm tmStartTime = to_tm(startTime);
1247 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1248 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec) + "/";
1249 create_directory(strTrashDir);
1250 }
1251 file.close();
1252 file.clear();
1253
1254 // Special code to replace our special files -- the Oni app, OniSplit, the Daodan DLL, and the GUI for OniSplit
1255 if (exists(strPathToEUFN + strOniApp))
1256 {
1257 if (exists(strOniApp))
1258 rename((path)strOniApp, (path)(strTrashDir + strOniApp));
1259 rename((path)(strPathToEUFN + strOniApp), (path)strOniApp);
1260 }
1261 if (updateAE->OniSplitVersion.compare(currentAE->OniSplitVersion) >= 1)
1262 {
1263 if (exists(strPathToEUFNInstall + strOniSplit))
1264 {
1265 if (exists(strOniSplit))
1266 rename((path)strOniSplit, (path)(strTrashDir + strOniSplit));
1267 rename((path)(strPathToEUFNInstall + strOniSplit), (path)strOniSplit);
1268 }
1269 }
1270#ifdef WIN32
1271 if (updateAE->DaodanVersion.compare(currentAE->DaodanVersion) >= 1)
1272 {
1273 if (exists(strPathToEUFN + strDaodan))
1274 {
1275 if (exists(("../" + strDaodan)))
1276 rename((path)("../" + strDaodan), (path)(strTrashDir + strDaodan));
1277 rename((path)(strPathToEUFN + strDaodan), (path)("../" + strDaodan));
1278 }
1279 }
1280 if (updateAE->WinGUIVersion.compare(currentAE->WinGUIVersion) >= 1)
1281 {
1282 if (exists(strPathToEUFNInstall + strWinGUI))
1283 {
1284 if (exists((path)strWinGUI))
1285 rename((path)strWinGUI, (path)(strTrashDir + strWinGUI));
1286 if (exists(strWinGUILang))
1287 rename((path)strWinGUILang, (path)(strTrashDir + strWinGUILang));
1288 rename((path)(strPathToEUFNInstall + strWinGUI), (path)strWinGUI);
1289 rename((path)(strPathToEUFNInstall + strWinGUILang), (path)strWinGUILang);
1290 }
1291 }
1292#else
1293 if (updateAE->MacGUIVersion.compare(currentAE->MacGUIVersion) >= 1)
1294 {
1295 if (exists(strPathToEUFN + strMacGUI))
1296 {
1297 if (exists(("../" + strMacGUI)))
1298 rename((path)("../" + strMacGUI), (path)(strTrashDir + strMacGUI));
1299 rename((path)(strPathToEUFN + strMacGUI), (path)("../" + strMacGUI));
1300 }
1301 }
1302#endif
1303
1304 // Now we trash whatever's in DeleteList; this allows us to clear out obsolete files in the previous AE install
1305 // Before moving a file to the Trash, we need to make sure each of the file's parent paths exists in the Trash...
1306 // ...so we iterate through the hierarchy of the file path, checking for each one and creating it if necessary
1307 for (vector<string>::iterator iter = updateAE->deleteList.begin(); iter != updateAE->deleteList.end(); iter++)
1308 {
1309 string thePath = *iter;
1310 if (exists((path)("../" + thePath)))
1311 {
1312 string aParentPath;
1313 string::size_type curPos = thePath.find("/", 0);
1314 if (curPos != string::npos)
1315 aParentPath = thePath.substr(0, curPos);
1316 string::size_type lastPos = curPos;
1317 while (curPos != string::npos && curPos < thePath.size())
1318 {
1319 aParentPath = aParentPath + thePath.substr(lastPos, curPos - lastPos);
1320 if (!exists(strTrashDir + aParentPath))
1321 create_directory(strTrashDir + aParentPath);
1322 lastPos = curPos + 1;
1323 curPos = thePath.find("/", lastPos);
1324 aParentPath = aParentPath + "/";
1325 }
1326 rename((path)("../" + thePath), (path)(strTrashDir + thePath));
1327 }
1328 }
1329
1330 // Now we crawl the update's package folders for newer versions and move them over, trashing ones that are already present
1331 vector<ModPackage> updatePackages, currentPackages;
1332 bool matchFound;
1333 updatePackages.reserve(256);
1334 currentPackages.reserve(256);
1335
1336 currentPackages = getPackages();
1337 updatePackages = getPackages(strPathToEUFNPackages);
1338
1339 for (vector<ModPackage>::iterator iter1 = updatePackages.begin(); iter1 != updatePackages.end(); iter1++)
1340 {
1341 matchFound = false;
1342 for (vector<ModPackage>::iterator iter2 = currentPackages.begin(); iter2 != currentPackages.end(); iter2++)
1343 {
1344 if (!iter1->modStringName.compare(iter2->modStringName))
1345 {
1346 matchFound = true;
1347 if (iter1->modStringVersion > iter2->modStringVersion)
1348 {
1349 rename((path)(strPathToPackages + iter2->modStringName), (path)(strTrashDir + iter2->modStringName));
1350 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1351 }
1352 }
1353 }
1354 if (!matchFound) // then there's no old package in the way, so just move in the one from the update
1355 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1356 }
1357
1358 // 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...
1359 // ...and folders which do not exist in the current AE will be created there
1360 vector<string> foldersToMake, filesToMove;
1361 string thePath;
1362 for (recursive_directory_iterator dir_itr(strPathToEUFNPackages + strGlobalize), end_itr; dir_itr != end_itr; ++dir_itr)
1363 {
1364 thePath = dir_itr->path().string();
1365 MakePathLocalToGlobalize(&thePath);
1366 if (is_regular_file(dir_itr->status()))
1367 {
1368 if (dir_itr->filename().at(0) != '.') // skip over dot-files, which are invisible in Unix
1369 filesToMove.push_back(thePath);
1370 }
1371 else if (is_directory(dir_itr->status()))
1372 {
1373 if (!exists(strPathToPackages + strGlobalize + thePath))
1374 foldersToMake.push_back(thePath);
1375 }
1376 }
1377 // Sort the foldersToMake strings by length, which is a fast solution to the problem of "How do we make sure we create folder 'parent/'...
1378 // ...before folder 'parent/child/'"?
1379 sort(foldersToMake.begin(), foldersToMake.end(), SortBySize); // SortBySize is a custom comparison function found later in this source file
1380 // First make the folders that don't exist in the current AE, so all the files have a place to go
1381 for (vector<string>::iterator iter = foldersToMake.begin(); iter != foldersToMake.end(); iter++)
1382 {
1383 create_directory(strPathToPackages + strGlobalize + *iter);
1384 }
1385 for (vector<string>::iterator iter = filesToMove.begin(); iter != filesToMove.end(); iter++)
1386 {
1387 if (exists(strPathToPackages + strGlobalize + *iter))
1388 rename((path)(strPathToPackages + strGlobalize + *iter), (path)(strTrashDir + *iter));
1389 rename((path)(strPathToEUFNPackages + strGlobalize + *iter), (path)(strPathToPackages + strGlobalize + *iter));
1390 }
1391
1392 // Clean up after ourselves, trashing any packages or programs in the update package that are not newer than the current AE
1393 create_directory(strTrashDir + "Unneeded update files");
1394 rename((path)strPathToEUFN, (path)(strTrashDir + "Unneeded update files/" + strEUFN));
1395
1396 // Write to log that we are finished with update
1397 ptime end_time(second_clock::local_time());
1398 string progressMsg2 = "Edition was updated to:\n" +
1399 updateAE->AEVersion + " on " + to_simple_string(end_time);
1400
1401 file.open("Update.log");
1402
1403 if(!file.fail())
1404 file.write(progressMsg2.c_str(), sizeof(progressMsg2.c_str()));
1405
1406 file.close();
1407 file.clear();
1408
1409 if (updateAE->globalizationRequired)
1410 CheckForGlobalization(true); // the 'true' value forces re-globalization
1411
1412 globalPackages = getPackages(); // refresh the list in memory
1413
1414 // TODO: Refresh the packages list in the window
1415
1416 return true;
1417}
1418
1419/* MakePathLocalToGlobalize is a function used once by ProcessAEUpdate() that takes a file in an \
1420| update's Globalize folder and changes its path, originally relative to the Installer, to be |
1421| relative to the structure of the Globalize folder; this makes it easier to have the Installer |
1422\ move said file from an update's Globalize folder to the current Globalize folder. */
1423void MakePathLocalToGlobalize(string *installerBasedPath)
1424{
1425 int deleteToHere = 0;
1426 deleteToHere = installerBasedPath->find("Globalize/");
1427 if (deleteToHere != 0)
1428 {
1429 deleteToHere += strlen("Globalize/");
1430 installerBasedPath->erase(0, deleteToHere);
1431 }
1432}
1433
1434/* SortBySize is a custom comparison function that we call when using the C++ sort() function in \
1435\ ProcessAEUpdate() on some strings that we want to sort by length. */
1436bool SortBySize(string a, string b)
1437{
1438 return (a.size() < b.size());
1439}
1440
1441//stolen token function...
1442void tokenize(const string& str, vector<string>& tokens, const string& delimiters)
1443{
1444 // Skip delimiters at beginning.
1445 string::size_type lastPos = str.find_first_not_of(delimiters, 0);
1446 // Find first "non-delimiter".
1447 string::size_type pos = str.find_first_of(delimiters, lastPos);
1448
1449 while (string::npos != pos || string::npos != lastPos)
1450 {
1451 // Found a token, add it to the vector.
1452 tokens.push_back(str.substr(lastPos, pos - lastPos));
1453 // Skip delimiters. Note the "not_of"
1454 lastPos = str.find_first_not_of(delimiters, pos);
1455 // Find next "non-delimiter"
1456 pos = str.find_first_of(delimiters, lastPos);
1457 }
1458}
1459
1460void clearOldDats(void) {
1461 directory_iterator end_iter_gdf;
1462 for ( directory_iterator dir_itr_gdf( "../GameDataFolder" );
1463 dir_itr_gdf != end_iter_gdf;
1464 ++dir_itr_gdf )
1465 {
1466 if ( dir_itr_gdf->path().extension() == ".dat" || dir_itr_gdf->path().extension() == ".raw" || dir_itr_gdf->path().extension() == ".sep" ) {
1467 remove( dir_itr_gdf->path() );
1468 }
1469
1470 }
1471
1472}
1473
1474// this function copies files and directories. If copying a
1475// directory to a directory, it copies recursively.
1476
1477//pardon the mess, I did this at midnight, and had to fix a bug
1478void copy( const path & from_ph,
1479 const path & to_ph )
1480{
1481 cout << to_ph.string() << "\n";
1482 // Make sure that the destination, if it exists, is a directory
1483 if((exists(to_ph) && !is_directory(to_ph)) || (!exists(from_ph))) cout << "error";
1484 if(!is_directory(from_ph))
1485 {
1486
1487 if(exists(to_ph))
1488 {
1489 copy_file(from_ph,to_ph/from_ph.filename());
1490 }
1491 else
1492 {
1493 try{
1494
1495 copy_file(from_ph,to_ph);
1496 }
1497 catch (exception ex){
1498 cout << from_ph.string() << " to " << to_ph.string() << "\n";
1499 }
1500 }
1501
1502 }
1503 else if(from_ph.filename() != ".svn")
1504 {
1505 path destination;
1506 if(!exists(to_ph))
1507 {
1508 destination=to_ph;
1509 }
1510 else
1511 {
1512 destination=to_ph/from_ph.filename();
1513 }
1514
1515 for(directory_iterator i(from_ph); i!=directory_iterator(); ++i)
1516 {
1517 //the idiot who coded this in the first place (not me)
1518 //forgot to make a new directory. Exception city. x_x
1519 create_directory(destination);
1520 copy(*i,destination/i->filename());
1521 }
1522 }
1523}
1524
1525void copy_directory( const path &from_dir_ph,
1526 const path &to_dir_ph)
1527{
1528 if(!exists(from_dir_ph) || !is_directory(from_dir_ph)
1529 || exists(to_dir_ph))
1530 cout << !exists(from_dir_ph) << " " << !is_directory(from_dir_ph)
1531 << " " << exists(to_dir_ph);
1532
1533# ifdef BOOST_POSIX
1534 struct stat from_stat;
1535 if ( (::stat( from_dir_ph.string().c_str(), &from_stat ) != 0)
1536 || ::mkdir(to_dir_ph.native_directory_string().c_str(),
1537 from_stat.st_mode)!=0)
1538# endif
1539 }
1540
1541string escapePath(string input)
1542{
1543 string output;
1544 string escape_me = "& ;()|<>\"'\\#*?$";
1545 for (unsigned int i = 0; i < input.size(); i++)
1546 {
1547 for (unsigned int j = 0; j < escape_me.size(); j++)
1548 if (input[i] == escape_me[j])
1549 output += '\\';
1550 output += input[i];
1551 }
1552 return output;
1553}
1554
1555Install_info_cfg::Install_info_cfg()
1556{
1557 AEVersion = "2009-07b";
1558 InstallerVersion = "1.0.1";
1559 DaodanVersion = "1.0";
1560 OniSplitVersion = "0.9.38.0";
1561 WinGUIVersion = "0";
1562 MacGUIVersion = "0";
1563 patch = false;
1564 globalizationRequired = false;
1565 deleteList.reserve(255);
1566}
1567
1568ModPackage::ModPackage()
1569{
1570 isInstalled = true; // replace with function
1571 name = "";
1572 modStringName = "";
1573 modStringVersion = 0;
1574 hasOnis = false;
1575 hasDeltas = false;
1576 hasBSL = false;
1577 hasAddon = false;
1578 hasDats = false;
1579 category = "";
1580 creator = "";
1581 isEngine = false;
1582 readme = "";
1583 globalNeeded = true;
1584}
1585#ifndef WIN32
1586void Sleep(int ms)
1587{
1588 sleep(ms / 1000);
1589}
1590#endif
1591#ifdef WIN32
1592
1593void RedirectIOToConsole()
1594{
1595 int hConHandle;
1596 long lStdHandle;
1597 CONSOLE_SCREEN_BUFFER_INFO coninfo;
1598 FILE *fp;
1599
1600 // allocate a console for this app
1601 AllocConsole();
1602
1603 // set the screen buffer to be big enough to let us scroll text
1604 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
1605 &coninfo);
1606 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
1607 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
1608 coninfo.dwSize);
1609
1610 // redirect unbuffered STDOUT to the console
1611 lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
1612 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1613 fp = _fdopen( hConHandle, "w" );
1614 *stdout = *fp;
1615 setvbuf( stdout, NULL, _IONBF, 0 );
1616
1617 // redirect unbuffered STDIN to the console
1618 lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
1619 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1620 fp = _fdopen( hConHandle, "r" );
1621 *stdin = *fp;
1622 setvbuf( stdin, NULL, _IONBF, 0 );
1623
1624 // redirect unbuffered STDERR to the console
1625 lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
1626 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1627 fp = _fdopen( hConHandle, "w" );
1628 *stderr = *fp;
1629 setvbuf( stderr, NULL, _IONBF, 0 );
1630
1631 // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
1632 // point to console as well
1633 ios::sync_with_stdio();
1634}
1635#endif
Note: See TracBrowser for help on using the repository browser.