1 /*
2  * Copyright (C) 2011-2012 Me and My Shadow
3  *
4  * This file is part of Me and My Shadow.
5  *
6  * Me and My Shadow is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Me and My Shadow is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Me and My Shadow.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "LevelPack.h"
21 #include "Functions.h"
22 #include "FileManager.h"
23 #include "TreeStorageNode.h"
24 #include "POASerializer.h"
25 #include "MD5.h"
26 #include <string.h>
27 #include <string>
28 #include <vector>
29 #include <fstream>
30 #include <algorithm>
31 #include <iostream>
32 using namespace std;
33 
34 //This is a special TreeStorageNode which only load node name/value and attributes, early exists when meeting any subnodes.
35 //This is used for fast loading of levels during game startup.
36 class LoadAttributesOnlyTreeStorageNode : public TreeStorageNode {
37 public:
newNode()38 	virtual ITreeStorageBuilder* newNode() override {
39 		//Early exit.
40 		return NULL;
41 	}
42 };
43 
LevelPack()44 LevelPack::LevelPack():currentLevel(0),loaded(false),levels(),customTheme(false){
45 	//We need to set the pointer to the dictionaryManager to NULL.
46 	dictionaryManager=NULL;
47 	//The type of levelpack is determined in the loadLevels method, but 'fallback' is CUSTOM.
48 	type=CUSTOM;
49 }
50 
~LevelPack()51 LevelPack::~LevelPack(){
52 	//We call clear, since that already takes care of the deletion, including the dictionaryManager.
53 	clear();
54 }
55 
clear()56 void LevelPack::clear(){
57 	currentLevel=0;
58 	loaded=false;
59 	levels.clear();
60 	levelpackDescription.clear();
61 	levelpackPath.clear();
62 	levelProgressFile.clear();
63 	congratulationText.clear();
64 	levelpackMusicList.clear();
65 
66 	//Also delete the dictionaryManager if it isn't null.
67 	if(dictionaryManager){
68 		delete dictionaryManager;
69 		dictionaryManager=NULL;
70 	}
71 }
72 
loadLevels(const std::string & levelListFile)73 bool LevelPack::loadLevels(const std::string& levelListFile){
74 	//We're going to load a new levellist so first clean any existing levels.
75 	clear();
76 
77 	//If the levelListFile is empty we have nothing to load so we return false.
78 	if(levelListFile.empty()){
79 		cerr<<"ERROR: No levellist file given."<<endl;
80 		return false;
81 	}
82 
83 	//Determine the levelpack type.
84 	if(levelListFile.find(getDataPath())==0){
85 		type=MAIN;
86 	}else if(levelListFile.find(getUserPath(USER_DATA)+"levelpacks/")==0){
87 		type=ADDON;
88 	}else{
89 		type=CUSTOM;
90 	}
91 
92 	levelpackPath=pathFromFileName(levelListFile);
93 
94 	//Create input streams for the levellist file.
95 	ifstream level(levelListFile.c_str());
96 
97 	if(!level){
98 		cerr<<"ERROR: Can't load level list "<<levelListFile<<endl;
99 		return false;
100 	}
101 
102 	//Load the level list file.
103 	TreeStorageNode obj;
104 	{
105 		POASerializer objSerializer;
106 		if(!objSerializer.readNode(level,&obj,true)){
107 			cerr<<"ERROR: Invalid file format of level list "<<levelListFile<<endl;
108 			return false;
109 		}
110 	}
111 
112 	//Check for folders inside the levelpack folder.
113 	{
114 		//Get all the sub directories.
115 		vector<string> v;
116 		v=enumAllDirs(pathFromFileName(levelListFile),false);
117 
118 		//Check if there's a locale folder containing translations.
119 		if(std::find(v.begin(),v.end(),"locale")!=v.end()){
120 			//Folder is present so configure the levelDictionaryManager.
121 			dictionaryManager=new tinygettext::DictionaryManager();
122 			dictionaryManager->set_use_fuzzy(false);
123 			dictionaryManager->add_directory(pathFromFileName(levelListFile)+"locale/");
124 			dictionaryManager->set_charset("UTF-8");
125 			dictionaryManager->set_language(tinygettext::Language::from_name(language));
126 		}else{
127 			dictionaryManager=NULL;
128 		}
129 
130 		//Check for a theme folder.
131 		if(std::find(v.begin(),v.end(),"theme")!=v.end()){
132 			customTheme=true;
133 		}
134 	}
135 
136 	//Look for the name.
137 	{
138 		vector<string> &v=obj.attributes["name"];
139 		if(!v.empty()){
140 			levelpackName=v[0];
141 		}else{
142 			//Name is not defined so take the folder name.
143 			levelpackName=pathFromFileName(levelListFile);
144 			//Remove the last character '/'
145 			levelpackName=levelpackName.substr(0,levelpackName.size()-1);
146 			levelpackName=fileNameFromPath(levelpackName);
147 		}
148 	}
149 
150 	//Look for the description.
151 	{
152 		vector<string> &v=obj.attributes["description"];
153 		if(!v.empty())
154 			levelpackDescription=v[0];
155 	}
156 
157 	//Look for the congratulation text.
158 	{
159 		vector<string> &v=obj.attributes["congratulations"];
160 		if(!v.empty())
161 			congratulationText=v[0];
162 	}
163 
164 	//Look for the music list.
165 	{
166 		vector<string> &v=obj.attributes["musiclist"];
167 		if(!v.empty())
168 			levelpackMusicList=v[0];
169 	}
170 
171 	//Loop through the level list entries.
172 	for(unsigned int i=0;i<obj.subNodes.size();i++){
173 		TreeStorageNode* obj1=obj.subNodes[i];
174 		if(obj1==NULL)
175 			continue;
176 		if(!obj1->value.empty() && obj1->name=="levelfile"){
177 			Level level;
178 			level.file=obj1->value[0];
179 			level.targetTime=0;
180 			level.targetRecordings=0;
181 			memset(level.md5Digest, 0, sizeof(level.md5Digest));
182 
183 			//The path to the file to open.
184 			//NOTE: In this function we are always loading levels from a level pack, so levelpackPath is always used.
185 			string levelFile=levelpackPath+level.file;
186 
187 			//Open the level file to retrieve the name and target time/recordings.
188 			LoadAttributesOnlyTreeStorageNode obj;
189 			POASerializer objSerializer;
190 			if(objSerializer.loadNodeFromFile(levelFile.c_str(),&obj,true)){
191 				//Get the name of the level.
192 				vector<string>& v=obj.attributes["name"];
193 				if(!v.empty())
194 					level.name=v[0];
195 				//If the name is empty then we set it to the file name.
196 				if(level.name.empty())
197 					level.name=fileNameFromPath(level.file);
198 
199 				//Get the target time of the level.
200 				v=obj.attributes["time"];
201 				if(!v.empty())
202 					level.targetTime=atoi(v[0].c_str());
203 				else
204 					level.targetTime=-1;
205 				//Get the target recordings of the level.
206 				v=obj.attributes["recordings"];
207 				if(!v.empty())
208 					level.targetRecordings=atoi(v[0].c_str());
209 				else
210 					level.targetRecordings=-1;
211 			}
212 
213 			//The default for locked is true, unless it's the first one.
214 			level.locked=!levels.empty();
215 			level.won=false;
216 			level.time=-1;
217 			level.recordings=-1;
218 
219 			//Add the level to the levels.
220 			levels.push_back(level);
221 		}
222 	}
223 
224 	loaded=true;
225 	return true;
226 }
227 
loadProgress()228 void LevelPack::loadProgress(){
229 	//Make sure that a levelProgressFile is set.
230 	if(levelProgressFile.empty()){
231 		levelProgressFile=getLevelProgressPath();
232 	}
233 
234 	//Open the file.
235 	ifstream levelProgress;
236 	levelProgress.open(processFileName(this->levelProgressFile).c_str());
237 
238 	//Check if the file exists.
239 	if(levelProgress){
240 		//Now load the progress/statistics.
241 		TreeStorageNode obj;
242 		{
243 			POASerializer objSerializer;
244 			if(!objSerializer.readNode(levelProgress,&obj,true)){
245 				cerr<<"ERROR: Invalid file format of level progress file."<<endl;
246 			}
247 		}
248 
249 		//Loop through the entries.
250 		for(unsigned int i=0;i<obj.subNodes.size();i++){
251 			TreeStorageNode* obj1=obj.subNodes[i];
252 			if(obj1==NULL)
253 				continue;
254 			if(!obj1->value.empty() && obj1->name=="level"){
255 				//We've found an entry for a level, now search the correct level.
256 				Level* level=NULL;
257 				for(unsigned int o=0;o<levels.size();o++){
258 					if(obj1->value[0]==levels[o].file){
259 						level=&levels[o];
260 						break;
261 					}
262 				}
263 
264 				//Check if we found the level.
265 				if(!level)
266 					continue;
267 
268 				//Get the progress/statistics.
269 				for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
270 					if(i->first=="locked"){
271 					level->locked=(i->second[0]=="1");
272 					}
273 					if(i->first=="won"){
274 						level->won=(i->second[0]=="1");
275 					}
276 					if(i->first=="time"){
277 						level->time=(atoi(i->second[0].c_str()));
278 					}
279 					if(i->first=="recordings"){
280 						level->recordings=(atoi(i->second[0].c_str()));
281 					}
282 				}
283 			}
284 		}
285 	}
286 }
287 
saveLevels(const std::string & levelListFile)288 void LevelPack::saveLevels(const std::string& levelListFile){
289 	//Get the fileName.
290 	string levelListNew=levelListFile;
291 	//Open an output stream.
292 	ofstream level(levelListNew.c_str());
293 
294 	//Check if we can use the file.
295 	if(!level){
296 		cerr<<"ERROR: Can't save level list "<<levelListNew<<endl;
297 		return;
298 	}
299 
300 	//Storage node that will contain the data that should be written.
301 	TreeStorageNode obj;
302 
303 	//Also store the name of the levelpack.
304 	if(!levelpackName.empty())
305 		obj.attributes["name"].push_back(levelpackName);
306 
307 	//Make sure that there's a description.
308 	if(!levelpackDescription.empty())
309 		obj.attributes["description"].push_back(levelpackDescription);
310 
311 	//Make sure that there's a congratulation text.
312 	if(!congratulationText.empty())
313 		obj.attributes["congratulations"].push_back(congratulationText);
314 
315 	//Make sure that there's a music list.
316 	if (!levelpackMusicList.empty())
317 		obj.attributes["musiclist"].push_back(levelpackMusicList);
318 
319 	//Add the levels to the file.
320 	for(unsigned int i=0;i<levels.size();i++){
321 		TreeStorageNode* obj1=new TreeStorageNode;
322 		obj1->name="levelfile";
323 		obj1->value.push_back(fileNameFromPath(levels[i].file));
324 		obj1->value.push_back(levels[i].name);
325 		obj.subNodes.push_back(obj1);
326 	}
327 
328 	//Write the it away.
329 	POASerializer objSerializer;
330 	objSerializer.writeNode(&obj,level,false,true);
331 }
332 
updateLanguage()333 void LevelPack::updateLanguage(){
334 	if(dictionaryManager!=NULL)
335 		dictionaryManager->set_language(tinygettext::Language::from_name(language));
336 }
337 
addLevel(const string & levelFileName,int levelno)338 void LevelPack::addLevel(const string& levelFileName,int levelno){
339 	//Fill in the details.
340 	Level level;
341 	if(type!=COLLECTION && !levelpackPath.empty() && levelFileName.compare(0,levelpackPath.length(),levelpackPath)==0){
342 		level.file=fileNameFromPath(levelFileName);
343 	}else{
344 		level.file=levelFileName;
345 	}
346 	level.targetTime=0;
347 	level.targetRecordings=0;
348 	memset(level.md5Digest, 0, sizeof(level.md5Digest));
349 
350 	//Get the name of the level.
351 	LoadAttributesOnlyTreeStorageNode obj;
352 	POASerializer objSerializer;
353 	if(objSerializer.loadNodeFromFile(levelFileName.c_str(),&obj,true)){
354 		//Get the name of the level.
355 		vector<string>& v=obj.attributes["name"];
356 		if(!v.empty())
357 			level.name=v[0];
358 		//If the name is empty then we set it to the file name.
359 		if(level.name.empty())
360 			level.name=fileNameFromPath(levelFileName);
361 
362 		//Get the target time of the level.
363 		v=obj.attributes["time"];
364 		if(!v.empty())
365 			level.targetTime=atoi(v[0].c_str());
366 		else
367 			level.targetTime=-1;
368 		//Get the target recordings of the level.
369 		v=obj.attributes["recordings"];
370 		if(!v.empty())
371 			level.targetRecordings=atoi(v[0].c_str());
372 		else
373 			level.targetRecordings=-1;
374 	}
375 	//Set if it should be locked or not.
376 	level.won=false;
377 	level.time=-1;
378 	level.recordings=-1;
379 	level.locked=levels.empty()?false:true;
380 
381 	//Check if the level should be at the end or somewhere in the middle.
382 	if(levelno<0 || levelno>=int(levels.size())){
383 		levels.push_back(level);
384 	}else{
385 		levels.insert(levels.begin()+levelno,level);
386 	}
387 
388 	//NOTE: We set loaded to true.
389 	loaded=true;
390 }
391 
moveLevel(unsigned int level1,unsigned int level2)392 void LevelPack::moveLevel(unsigned int level1,unsigned int level2){
393 	if(level1>=levels.size())
394 		return;
395 	if(level2>=levels.size())
396 		return;
397 	if(level1==level2)
398 		return;
399 
400 	levels.insert(levels.begin()+level2,levels[level1]);
401 	if(level2<=level1)
402 		levels.erase(levels.begin()+level1+1);
403 	else
404 		levels.erase(levels.begin()+level1);
405 }
406 
saveLevelProgress()407 void LevelPack::saveLevelProgress(){
408 	//Check if the levels are loaded and a progress file is given.
409 	if(!loaded || levelProgressFile.empty())
410 		return;
411 
412 	//Open the progress file.
413 	ofstream levelProgress(processFileName(levelProgressFile).c_str());
414 	if(!levelProgress)
415 		return;
416 
417 	//Open an output stream.
418 	TreeStorageNode node;
419 
420 	//Loop through the levels.
421 	for(unsigned int o=0;o<levels.size();o++){
422 		TreeStorageNode* obj=new TreeStorageNode;
423 		node.subNodes.push_back(obj);
424 
425 		char s[64];
426 
427 		//Set the name of the node.
428 		obj->name="level";
429 		obj->value.push_back(levels[o].file);
430 
431 		//Set the values.
432 		obj->attributes["locked"].push_back(levels[o].locked?"1":"0");
433 		obj->attributes["won"].push_back(levels[o].won?"1":"0");
434 		sprintf(s,"%d",levels[o].time);
435 		obj->attributes["time"].push_back(s);
436 		sprintf(s,"%d",levels[o].recordings);
437 		obj->attributes["recordings"].push_back(s);
438 	}
439 
440 
441 	//Create a POASerializer and write away the leve node.
442 	POASerializer objSerializer;
443 	objSerializer.writeNode(&node,levelProgress,true,true);
444 }
445 
getLevelName(int level)446 const string& LevelPack::getLevelName(int level){
447 	if(level<0)
448 		level=currentLevel;
449 	return levels[level].name;
450 }
451 
getLevelMD5(int level)452 const unsigned char* LevelPack::getLevelMD5(int level){
453 	if(level<0)
454 		level=currentLevel;
455 
456 	//Check if the md5Digest is not initialized.
457 	bool notInitialized = true;
458 	for (int i = 0; i < 16; i++) {
459 		if (levels[level].md5Digest[i]) {
460 			notInitialized = false;
461 			break;
462 		}
463 	}
464 
465 	//Calculate md5Digest if needed.
466 	if (notInitialized) {
467 		string levelFile = getLevelFile(level);
468 
469 		TreeStorageNode obj;
470 		POASerializer objSerializer;
471 		if (objSerializer.loadNodeFromFile(levelFile.c_str(), &obj, true)) {
472 			obj.name.clear();
473 			obj.calcMD5(levels[level].md5Digest);
474 		} else {
475 			cerr << "ERROR: Failed to load file '" << levelFile << "' for calculating MD5" << endl;
476 
477 			//Fill in a fake MD5
478 			for (int i = 0; i < 16; i++) {
479 				levels[level].md5Digest[i] = 0xCC;
480 			}
481 		}
482 	}
483 
484 	return levels[level].md5Digest;
485 }
486 
getLevelAutoSaveRecordPath(int level,std::string & bestTimeFilePath,std::string & bestRecordingFilePath,bool createPath)487 void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
488 	if(level<0)
489 		level=currentLevel;
490 
491 	bestTimeFilePath.clear();
492 	bestRecordingFilePath.clear();
493 
494 	//get level pack path.
495 	string levelpackPath = (type == COLLECTION ? std::string() : LevelPack::levelpackPath);
496 	string s=levels[level].file;
497 
498 	//process level pack name
499 	for(;;){
500 		string::size_type lps=levelpackPath.find_last_of("/\\");
501 		if(lps==string::npos){
502 			break;
503 		}else if(lps==levelpackPath.size()-1){
504 			levelpackPath.resize(lps);
505 		}else{
506 			levelpackPath=levelpackPath.substr(lps+1);
507 			break;
508 		}
509 	}
510 
511 	//profess file name
512 	{
513 		string::size_type lps=s.find_last_of("/\\");
514 		if(lps!=string::npos) s=s.substr(lps+1);
515 	}
516 
517 	//check if it's custom level
518 	{
519 		string path="%USER%/records/autosave/";
520 		if(!levelpackPath.empty()){
521 			path+=levelpackPath;
522 			path+='/';
523 		}
524 		path=processFileName(path);
525 		if(createPath) createDirectory(path.c_str());
526 		s=path+s;
527 	}
528 
529 	//calculate MD5
530 	s+='-';
531 	s += Md5::toString(getLevelMD5(level));
532 
533 	//over
534 	bestTimeFilePath=s+"-best-time.mnmsrec";
535 	bestRecordingFilePath=s+"-best-recordings.mnmsrec";
536 }
537 
getLevelProgressPath()538 string LevelPack::getLevelProgressPath(){
539 	if(levelProgressFile.empty()){
540 		levelProgressFile="%USER%/progress/";
541 
542 		//Use the levelpack folder name instead of the levelpack name.
543 		//NOTE: Remove the trailing slash.
544 		string folderName=levelpackPath.substr(0,levelpackPath.size()-1);
545 		folderName=fileNameFromPath(folderName);
546 
547 		//Depending on the levelpack type add a folder.
548 		switch(type){
549 			case MAIN:
550 				levelProgressFile+="main/";
551 				levelProgressFile+=folderName+".progress";
552 				break;
553 			case ADDON:
554 				levelProgressFile+="addon/";
555 				levelProgressFile+=folderName+".progress";
556 				break;
557 			case CUSTOM:
558 				levelProgressFile+="custom/";
559 				levelProgressFile+=folderName+".progress";
560 				break;
561 			case COLLECTION:
562 				//NOTE: For collections we use their name since they don't have a folder.
563 				//FIXME: Make sure the name contains legal characters.
564 				levelProgressFile+=levelpackName+".progress";
565 				break;
566 		}
567 	}
568 
569 	return levelProgressFile;
570 }
571 
setLevelName(unsigned int level,const std::string & name)572 void LevelPack::setLevelName(unsigned int level,const std::string& name){
573 	if(level<levels.size())
574 		levels[level].name=name;
575 }
576 
getLevelFile(int level)577 const string LevelPack::getLevelFile(int level){
578 	if(level<0)
579 		level=currentLevel;
580 
581 	string levelFile;
582 	if(type!=COLLECTION)
583 		levelFile=levelpackPath+levels[level].file;
584 	else
585 		levelFile=levels[level].file;
586 
587 	return levelFile;
588 }
589 
getLevelpackPath()590 const string& LevelPack::getLevelpackPath(){
591 	return levelpackPath;
592 }
593 
getLevel(int level)594 struct LevelPack::Level* LevelPack::getLevel(int level){
595 	if(level<0)
596 		return &levels[currentLevel];
597 	return &levels[level];
598 }
599 
resetLevel(int level)600 void LevelPack::resetLevel(int level){
601 	if(level<0)
602 		level=currentLevel;
603 
604 	//Set back to default.
605 	levels[level].locked=(level!=0);
606 	levels[level].won=false;
607 	levels[level].time=-1;
608 	levels[level].recordings=-1;
609 }
610 
nextLevel()611 void LevelPack::nextLevel(){
612 	currentLevel++;
613 }
614 
getLocked(unsigned int level)615 bool LevelPack::getLocked(unsigned int level){
616 	return levels[level].locked;
617 }
618 
setCurrentLevel(unsigned int level)619 void LevelPack::setCurrentLevel(unsigned int level){
620 	currentLevel=level;
621 }
622 
setLocked(unsigned int level,bool locked)623 void LevelPack::setLocked(unsigned int level,bool locked){
624 	levels[level].locked=locked;
625 }
626 
swapLevel(unsigned int level1,unsigned int level2)627 void LevelPack::swapLevel(unsigned int level1,unsigned int level2){
628 	if(level1<levels.size()&&level2<levels.size()){
629 		swap(levels[level1],levels[level2]);
630 	}
631 }
632 
removeLevel(unsigned int level)633 void LevelPack::removeLevel(unsigned int level){
634 	if(level<levels.size()){
635 		levels.erase(levels.begin()+level);
636 	}
637 }
638