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

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

Windows fixes.

File size: 61.0 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 popenCommand = "replace_installer.bat";
1146#else
1147 // We can't just use '~' to mean "the home directory" because we need to check the path in C...
1148 // ...so we actually get the current user's shortname and manually construct the path to home
1149 FILE *fUserName = NULL;
1150 char chrUserName[32];
1151 fUserName = popen("whoami", "r");
1152 fgets(chrUserName, sizeof(chrUserName), fUserName);
1153 pclose(fUserName);
1154 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1155 int endOfName = strUserName.find("\n", 0);
1156 string pathToTrash = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1157 tm tmStartTime = to_tm(startTime);
1158 pathToTrash = pathToTrash + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1159 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec); // lol
1160 create_directory(pathToTrash);
1161 // The script takes as a parameter the path the old Installer should go to, in quotes
1162 popenCommand = "bash " + popenCommand + "replace_installer.sh " + pathToTrash + "/Installer.app";
1163
1164#endif
1165 file.close();
1166 file.clear();
1167#ifdef WIN32
1168 system(popenCommand.c_str());
1169#else
1170 popen(popenCommand.c_str(), "r");
1171#endif
1172 return true; // returning 'true' tells the Installer to quit itself ASAP so it can be replaced by the process that is now running
1173}
1174
1175bool ProcessAEUpdate(Install_info_cfg *currentAE, Install_info_cfg *updateAE, bool *installerJustUpdated)
1176{
1177 fstream file;
1178 string line;
1179 vector<string> tokens, updateStarted;
1180 string strInstaller = "Installer";
1181 string strWas = "was";
1182 string strPathToEUFN = ("../updates/" + strEUFN + "/"); // strEUFN is set by GetUpdateStatus()
1183 string strPathToEUFNInstall = ("../updates/" + strEUFN + "/install/");
1184 string strPathToEUFNPackages = ("../updates/" + strEUFN + "/install/packages/");
1185 string strPathToPackages = "packages/";
1186 string strGlobalize = "Globalize/";
1187 string strOniSplit = "OniSplit.exe";
1188 string strDaodan = "binkw32.dll";
1189 string strWinGUI = "onisplit_gui.exe";
1190 string strWinGUILang = "ospgui_lang.ini";
1191 string strMacGUI = "AETools.app";
1192#ifdef WIN32
1193 string strOniApp = "Oni.exe";
1194#else
1195 string strOniApp = "Oni.app";
1196#endif
1197 bool needNewTrashDir = false;
1198 bool readingVerAndDate = false;
1199
1200 // TODO: Fill in Windows equivalent of code below
1201#ifdef WIN32
1202 string strTrashDir = "Trash\\";
1203#else
1204 FILE *fUserName = NULL;
1205 char chrUserName[32];
1206 fUserName = popen("whoami", "r");
1207 fgets(chrUserName, sizeof(chrUserName), fUserName);
1208 pclose(fUserName);
1209 string strUserName = (string)chrUserName; // stringsblaaarrrgggghhhh
1210 int endOfName = strUserName.find("\n", 0);
1211 string strTrashDir = "/Users/" + strUserName.substr(0, endOfName) + "/.Trash/";
1212#endif
1213
1214 // Write to log that we are beginning the update process
1215 ptime startTime(second_clock::local_time());
1216 string strStartTime = to_simple_string(startTime);
1217 string progressMsg = "\nEdition being updated to:\n" +
1218 updateAE->AEVersion + " on " + strStartTime;
1219 file.open("Update.log");
1220 if (!file.fail())
1221 file << progressMsg.c_str();
1222
1223 if (*installerJustUpdated) // then we want to know what folder in the Trash the Installer was placed in...
1224 {
1225 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
1226 {
1227 getline(file, line);
1228 tokenize(line, tokens);
1229
1230 if (tokens.capacity() >= 4)
1231 if (!strInstaller.compare(tokens[0]))
1232 if (!strWas.compare(tokens[1]))
1233 readingVerAndDate = true;
1234 if (readingVerAndDate && tokens.capacity() >= 3)
1235 tokenize(tokens[2], updateStarted, "-");
1236 }
1237 if (updateStarted.capacity() < 3)
1238 needNewTrashDir = true;
1239 else
1240 {
1241 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "-" +
1242 updateStarted[0] + "-" + updateStarted[1] + "-" + updateStarted[2] + "/";
1243 if (!exists(strTrashDir))
1244 needNewTrashDir = true;
1245 }
1246 }
1247#ifndef WIN32
1248 if (!*installerJustUpdated || needNewTrashDir) // prepare a new directory for deleted files to go to
1249 {
1250 tm tmStartTime = to_tm(startTime);
1251 strTrashDir = strTrashDir + "Old_Edition_files_" + currentAE->AEVersion + "_" + boost::lexical_cast<string>(tmStartTime.tm_hour) + "-" +
1252 boost::lexical_cast<string>(tmStartTime.tm_min) + "-" + boost::lexical_cast<string>(tmStartTime.tm_sec) + "/";
1253 create_directory(strTrashDir);
1254 }
1255#endif
1256 file.close();
1257 file.clear();
1258
1259 // Special code to replace our special files -- the Oni app, OniSplit, the Daodan DLL, and the GUI for OniSplit
1260 if (exists(strPathToEUFN + strOniApp))
1261 {
1262 if (exists(strOniApp))
1263 rename((path)strOniApp, (path)(strTrashDir + strOniApp));
1264 rename((path)(strPathToEUFN + strOniApp), (path)strOniApp);
1265 }
1266 if (updateAE->OniSplitVersion.compare(currentAE->OniSplitVersion) >= 1)
1267 {
1268 if (exists(strPathToEUFNInstall + strOniSplit))
1269 {
1270 if (exists(strOniSplit))
1271 rename((path)strOniSplit, (path)(strTrashDir + strOniSplit));
1272 rename((path)(strPathToEUFNInstall + strOniSplit), (path)strOniSplit);
1273 }
1274 }
1275#ifdef WIN32
1276 if (updateAE->DaodanVersion.compare(currentAE->DaodanVersion) >= 1)
1277 {
1278 if (exists(strPathToEUFN + strDaodan))
1279 {
1280 if (exists(("../" + strDaodan)))
1281 rename((path)("../" + strDaodan), (path)(strTrashDir + strDaodan));
1282 rename((path)(strPathToEUFN + strDaodan), (path)("../" + strDaodan));
1283 }
1284 }
1285 if (updateAE->WinGUIVersion.compare(currentAE->WinGUIVersion) >= 1)
1286 {
1287 if (exists(strPathToEUFNInstall + strWinGUI))
1288 {
1289 if (exists((path)strWinGUI))
1290 rename((path)strWinGUI, (path)(strTrashDir + strWinGUI));
1291 if (exists(strWinGUILang))
1292 rename((path)strWinGUILang, (path)(strTrashDir + strWinGUILang));
1293 rename((path)(strPathToEUFNInstall + strWinGUI), (path)strWinGUI);
1294 rename((path)(strPathToEUFNInstall + strWinGUILang), (path)strWinGUILang);
1295 }
1296 }
1297#else
1298 if (updateAE->MacGUIVersion.compare(currentAE->MacGUIVersion) >= 1)
1299 {
1300 if (exists(strPathToEUFN + strMacGUI))
1301 {
1302 if (exists(("../" + strMacGUI)))
1303 rename((path)("../" + strMacGUI), (path)(strTrashDir + strMacGUI));
1304 rename((path)(strPathToEUFN + strMacGUI), (path)("../" + strMacGUI));
1305 }
1306 }
1307#endif
1308
1309 // Now we trash whatever's in DeleteList; this allows us to clear out obsolete files in the previous AE install
1310 // Before moving a file to the Trash, we need to make sure each of the file's parent paths exists in the Trash...
1311 // ...so we iterate through the hierarchy of the file path, checking for each one and creating it if necessary
1312 for (vector<string>::iterator iter = updateAE->deleteList.begin(); iter != updateAE->deleteList.end(); iter++)
1313 {
1314 string thePath = *iter;
1315 if (exists((path)("../" + thePath)))
1316 {
1317 string aParentPath;
1318 string::size_type curPos = thePath.find("/", 0);
1319 if (curPos != string::npos)
1320 aParentPath = thePath.substr(0, curPos);
1321 string::size_type lastPos = curPos;
1322 while (curPos != string::npos && curPos < thePath.size())
1323 {
1324 aParentPath = aParentPath + thePath.substr(lastPos, curPos - lastPos);
1325 if (!exists(strTrashDir + aParentPath))
1326 create_directory(strTrashDir + aParentPath);
1327 lastPos = curPos + 1;
1328 curPos = thePath.find("/", lastPos);
1329 aParentPath = aParentPath + "/";
1330 }
1331#ifndef WIN32
1332 rename((path)("../" + thePath), (path)(strTrashDir + thePath));
1333#else
1334 remove((path)("../" + thePath));
1335#endif
1336 }
1337 }
1338
1339 // Now we crawl the update's package folders for newer versions and move them over, trashing ones that are already present
1340 vector<ModPackage> updatePackages, currentPackages;
1341 bool matchFound;
1342 updatePackages.reserve(256);
1343 currentPackages.reserve(256);
1344
1345 currentPackages = getPackages();
1346 updatePackages = getPackages(strPathToEUFNPackages);
1347
1348 for (vector<ModPackage>::iterator iter1 = updatePackages.begin(); iter1 != updatePackages.end(); iter1++)
1349 {
1350 matchFound = false;
1351 for (vector<ModPackage>::iterator iter2 = currentPackages.begin(); iter2 != currentPackages.end(); iter2++)
1352 {
1353 if (!iter1->modStringName.compare(iter2->modStringName))
1354 {
1355 matchFound = true;
1356 if (iter1->modStringVersion > iter2->modStringVersion)
1357 {
1358#ifndef WIN32
1359 rename((path)(strPathToPackages + iter2->modStringName), (path)(strTrashDir + iter2->modStringName));
1360#else
1361 remove((path)(strPathToPackages + iter2->modStringName));
1362#endif
1363 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1364 }
1365 }
1366 }
1367 if (!matchFound) // then there's no old package in the way, so just move in the one from the update
1368 rename((path)(strPathToEUFNPackages + iter1->modStringName), (path)(strPathToPackages + iter1->modStringName));
1369 }
1370
1371 // 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...
1372 // ...and folders which do not exist in the current AE will be created there
1373 vector<string> foldersToMake, filesToMove;
1374 string thePath;
1375 for (recursive_directory_iterator dir_itr(strPathToEUFNPackages + strGlobalize), end_itr; dir_itr != end_itr; ++dir_itr)
1376 {
1377 thePath = dir_itr->path().string();
1378 MakePathLocalToGlobalize(&thePath);
1379 if (is_regular_file(dir_itr->status()))
1380 {
1381 if (dir_itr->filename().at(0) != '.') // skip over dot-files, which are invisible in Unix
1382 filesToMove.push_back(thePath);
1383 }
1384 else if (is_directory(dir_itr->status()))
1385 {
1386 if (!exists(strPathToPackages + strGlobalize + thePath))
1387 foldersToMake.push_back(thePath);
1388 }
1389 }
1390 // Sort the foldersToMake strings by length, which is a fast solution to the problem of "How do we make sure we create folder 'parent/'...
1391 // ...before folder 'parent/child/'"?
1392 sort(foldersToMake.begin(), foldersToMake.end(), SortBySize); // SortBySize is a custom comparison function found later in this source file
1393 // First make the folders that don't exist in the current AE, so all the files have a place to go
1394 for (vector<string>::iterator iter = foldersToMake.begin(); iter != foldersToMake.end(); iter++)
1395 {
1396 create_directory(strPathToPackages + strGlobalize + *iter);
1397 }
1398 for (vector<string>::iterator iter = filesToMove.begin(); iter != filesToMove.end(); iter++)
1399 {
1400 if (exists(strPathToPackages + strGlobalize + *iter))
1401 rename((path)(strPathToPackages + strGlobalize + *iter), (path)(strTrashDir + *iter));
1402 rename((path)(strPathToEUFNPackages + strGlobalize + *iter), (path)(strPathToPackages + strGlobalize + *iter));
1403 }
1404
1405 // Clean up after ourselves, trashing any packages or programs in the update package that are not newer than the current AE
1406 create_directory(strTrashDir + "Unneeded update files");
1407 rename((path)strPathToEUFN, (path)(strTrashDir + "Unneeded update files/" + strEUFN));
1408
1409 // Write to log that we are finished with update
1410 ptime end_time(second_clock::local_time());
1411 string progressMsg2 = "Edition was updated to:\n" +
1412 updateAE->AEVersion + " on " + to_simple_string(end_time);
1413
1414 file.open("Update.log");
1415
1416 if(!file.fail())
1417 file.write(progressMsg2.c_str(), sizeof(progressMsg2.c_str()));
1418
1419 file.close();
1420 file.clear();
1421
1422 if (updateAE->globalizationRequired)
1423 CheckForGlobalization(true); // the 'true' value forces re-globalization
1424
1425 globalPackages = getPackages(); // refresh the list in memory
1426
1427 // TODO: Refresh the packages list in the window
1428
1429 return true;
1430}
1431
1432/* MakePathLocalToGlobalize is a function used once by ProcessAEUpdate() that takes a file in an \
1433| update's Globalize folder and changes its path, originally relative to the Installer, to be |
1434| relative to the structure of the Globalize folder; this makes it easier to have the Installer |
1435\ move said file from an update's Globalize folder to the current Globalize folder. */
1436void MakePathLocalToGlobalize(string *installerBasedPath)
1437{
1438 int deleteToHere = 0;
1439 deleteToHere = installerBasedPath->find("Globalize/");
1440 if (deleteToHere != 0)
1441 {
1442 deleteToHere += strlen("Globalize/");
1443 installerBasedPath->erase(0, deleteToHere);
1444 }
1445}
1446
1447/* SortBySize is a custom comparison function that we call when using the C++ sort() function in \
1448\ ProcessAEUpdate() on some strings that we want to sort by length. */
1449bool SortBySize(string a, string b)
1450{
1451 return (a.size() < b.size());
1452}
1453
1454//stolen token function...
1455void tokenize(const string& str, vector<string>& tokens, const string& delimiters)
1456{
1457 // Skip delimiters at beginning.
1458 string::size_type lastPos = str.find_first_not_of(delimiters, 0);
1459 // Find first "non-delimiter".
1460 string::size_type pos = str.find_first_of(delimiters, lastPos);
1461
1462 while (string::npos != pos || string::npos != lastPos)
1463 {
1464 // Found a token, add it to the vector.
1465 tokens.push_back(str.substr(lastPos, pos - lastPos));
1466 // Skip delimiters. Note the "not_of"
1467 lastPos = str.find_first_not_of(delimiters, pos);
1468 // Find next "non-delimiter"
1469 pos = str.find_first_of(delimiters, lastPos);
1470 }
1471}
1472
1473void clearOldDats(void) {
1474 directory_iterator end_iter_gdf;
1475 for ( directory_iterator dir_itr_gdf( "../GameDataFolder" );
1476 dir_itr_gdf != end_iter_gdf;
1477 ++dir_itr_gdf )
1478 {
1479 if ( dir_itr_gdf->path().extension() == ".dat" || dir_itr_gdf->path().extension() == ".raw" || dir_itr_gdf->path().extension() == ".sep" ) {
1480 remove( dir_itr_gdf->path() );
1481 }
1482
1483 }
1484
1485}
1486
1487// this function copies files and directories. If copying a
1488// directory to a directory, it copies recursively.
1489
1490//pardon the mess, I did this at midnight, and had to fix a bug
1491void copy( const path & from_ph,
1492 const path & to_ph )
1493{
1494 cout << to_ph.string() << "\n";
1495 // Make sure that the destination, if it exists, is a directory
1496 if((exists(to_ph) && !is_directory(to_ph)) || (!exists(from_ph))) cout << "error";
1497 if(!is_directory(from_ph))
1498 {
1499
1500 if(exists(to_ph))
1501 {
1502 copy_file(from_ph,to_ph/from_ph.filename());
1503 }
1504 else
1505 {
1506 try{
1507
1508 copy_file(from_ph,to_ph);
1509 }
1510 catch (exception ex){
1511 cout << from_ph.string() << " to " << to_ph.string() << "\n";
1512 }
1513 }
1514
1515 }
1516 else if(from_ph.filename() != ".svn")
1517 {
1518 path destination;
1519 if(!exists(to_ph))
1520 {
1521 destination=to_ph;
1522 }
1523 else
1524 {
1525 destination=to_ph/from_ph.filename();
1526 }
1527
1528 for(directory_iterator i(from_ph); i!=directory_iterator(); ++i)
1529 {
1530 //the idiot who coded this in the first place (not me)
1531 //forgot to make a new directory. Exception city. x_x
1532 create_directory(destination);
1533 copy(*i,destination/i->filename());
1534 }
1535 }
1536}
1537
1538void copy_directory( const path &from_dir_ph,
1539 const path &to_dir_ph)
1540{
1541 if(!exists(from_dir_ph) || !is_directory(from_dir_ph)
1542 || exists(to_dir_ph))
1543 cout << !exists(from_dir_ph) << " " << !is_directory(from_dir_ph)
1544 << " " << exists(to_dir_ph);
1545
1546# ifdef BOOST_POSIX
1547 struct stat from_stat;
1548 if ( (::stat( from_dir_ph.string().c_str(), &from_stat ) != 0)
1549 || ::mkdir(to_dir_ph.native_directory_string().c_str(),
1550 from_stat.st_mode)!=0)
1551# endif
1552 }
1553
1554string escapePath(string input)
1555{
1556 string output;
1557 string escape_me = "& ;()|<>\"'\\#*?$";
1558 for (unsigned int i = 0; i < input.size(); i++)
1559 {
1560 for (unsigned int j = 0; j < escape_me.size(); j++)
1561 if (input[i] == escape_me[j])
1562 output += '\\';
1563 output += input[i];
1564 }
1565 return output;
1566}
1567
1568Install_info_cfg::Install_info_cfg()
1569{
1570 AEVersion = "2009-07b";
1571 InstallerVersion = "1.0.1";
1572 DaodanVersion = "1.0";
1573 OniSplitVersion = "0.9.38.0";
1574 WinGUIVersion = "0";
1575 MacGUIVersion = "0";
1576 patch = false;
1577 globalizationRequired = false;
1578 deleteList.reserve(255);
1579}
1580
1581ModPackage::ModPackage()
1582{
1583 isInstalled = true; // replace with function
1584 name = "";
1585 modStringName = "";
1586 modStringVersion = 0;
1587 hasOnis = false;
1588 hasDeltas = false;
1589 hasBSL = false;
1590 hasAddon = false;
1591 hasDats = false;
1592 category = "";
1593 creator = "";
1594 isEngine = false;
1595 readme = "";
1596 globalNeeded = true;
1597}
1598#ifndef WIN32
1599void Sleep(int ms)
1600{
1601 sleep(ms / 1000);
1602}
1603#endif
1604#ifdef WIN32
1605
1606void RedirectIOToConsole()
1607{
1608 int hConHandle;
1609 long lStdHandle;
1610 CONSOLE_SCREEN_BUFFER_INFO coninfo;
1611 FILE *fp;
1612
1613 // allocate a console for this app
1614 AllocConsole();
1615
1616 // set the screen buffer to be big enough to let us scroll text
1617 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
1618 &coninfo);
1619 coninfo.dwSize.Y = MAX_CONSOLE_LINES;
1620 SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
1621 coninfo.dwSize);
1622
1623 // redirect unbuffered STDOUT to the console
1624 lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
1625 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1626 fp = _fdopen( hConHandle, "w" );
1627 *stdout = *fp;
1628 setvbuf( stdout, NULL, _IONBF, 0 );
1629
1630 // redirect unbuffered STDIN to the console
1631 lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
1632 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1633 fp = _fdopen( hConHandle, "r" );
1634 *stdin = *fp;
1635 setvbuf( stdin, NULL, _IONBF, 0 );
1636
1637 // redirect unbuffered STDERR to the console
1638 lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
1639 hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
1640 fp = _fdopen( hConHandle, "w" );
1641 *stderr = *fp;
1642 setvbuf( stderr, NULL, _IONBF, 0 );
1643
1644 // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
1645 // point to console as well
1646 ios::sync_with_stdio();
1647}
1648#endif
Note: See TracBrowser for help on using the repository browser.