1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis 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  * Arx Libertatis 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 Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* Based on:
20 ===========================================================================
21 ARX FATALIS GPL Source Code
22 Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
23 
24 This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
25 
26 Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
27 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
28 
29 Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
30 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
31 
32 You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code.  If not, see
33 <http://www.gnu.org/licenses/>.
34 
35 In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
36 additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
37 Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
38 
39 If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
40 ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
41 ===========================================================================
42 */
43 
44 #include "io/SaveBlock.h"
45 
46 #include <cstdlib>
47 
48 #include <boost/algorithm/string/case_conv.hpp>
49 
50 #include <zlib.h>
51 
52 #include "io/log/Logger.h"
53 #include "io/fs/Filesystem.h"
54 #include "io/Blast.h"
55 
56 #include "platform/Platform.h"
57 
58 using std::string;
59 using std::vector;
60 using std::min;
61 
62 static const u32 SAV_VERSION_OLD = (1<<16) | 0;
63 static const u32 SAV_VERSION_RELEASE = (1<<16) | 1;
64 static const u32 SAV_VERSION_DEFLATE = (2<<16) | 0;
65 static const u32 SAV_VERSION_NOEXT = (2<<16) | 1;
66 
67 static const u32 SAV_COMP_NONE = 0;
68 static const u32 SAV_COMP_IMPLODE = 1;
69 static const u32 SAV_COMP_DEFLATE = 2;
70 
71 static const u32 SAV_SIZE_UNKNOWN = 0xffffffff;
72 
73 #ifdef ARX_DEBUG
74 static const char BADSAVCHAR[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\\/.";
75 #endif
76 
compressionName() const77 const char * SaveBlock::File::compressionName() const {
78 	switch(comp) {
79 		case None: return "none";
80 		case ImplodeCrypt: return "implode+crypt";
81 		case Deflate: return "deflate";
82 		default: return "(unknown)";
83 	}
84 }
85 
loadOffsets(std::istream & handle,u32 version)86 bool SaveBlock::File::loadOffsets(std::istream & handle, u32 version) {
87 
88 	if(version < SAV_VERSION_DEFLATE) {
89 		// ignore the size, calculate from chunks
90 		handle.seekg(4, std::istream::cur);
91 		uncompressedSize = size_t(-1);
92 	} else {
93 		u32 uncompressed;
94 		if(fs::read(handle, uncompressed).fail()) {
95 			return false;
96 		}
97 		uncompressedSize = (uncompressed == SAV_SIZE_UNKNOWN) ? size_t(-1) : uncompressed;
98 	}
99 
100 	u32 nChunks;
101 	if(!fs::read(handle, nChunks)) {
102 		return false;
103 	}
104 	if(version < SAV_VERSION_DEFLATE && nChunks == 0) {
105 		nChunks = 1;
106 	}
107 	chunks.resize(nChunks);
108 
109 	if(version < SAV_VERSION_DEFLATE) {
110 		// ignored
111 		handle.seekg(4, std::istream::cur);
112 		comp = File::ImplodeCrypt;
113 	} else {
114 		u32 compid;
115 		if(!fs::read(handle, compid)) {
116 			return false;
117 		}
118 		switch(compid) {
119 			case SAV_COMP_NONE: comp = File::None; break;
120 			case SAV_COMP_IMPLODE: comp = File::ImplodeCrypt; break;
121 			case SAV_COMP_DEFLATE: comp = File::Deflate; break;
122 			default: comp = File::Unknown;
123 		}
124 	}
125 
126 	size_t size = 0;
127 	for(size_t i = 0; i < nChunks; i++) {
128 
129 		u32 chunkSize;
130 		if(!fs::read(handle, chunkSize)) {
131 			return false;
132 		}
133 		size += chunkSize;
134 
135 		u32 chunkOffset;
136 		if(!fs::read(handle, chunkOffset)) {
137 			return false;
138 		}
139 
140 		chunks[i].size = chunkSize;
141 		chunks[i].offset = chunkOffset;
142 	}
143 
144 	storedSize = size;
145 
146 	return true;
147 }
148 
writeEntry(std::ostream & handle,const std::string & name) const149 void SaveBlock::File::writeEntry(std::ostream & handle, const std::string & name) const {
150 
151 	handle.write(name.c_str(), name.length() + 1);
152 
153 	u32 _uncompressedSize = (uncompressedSize == size_t(-1)) ? SAV_SIZE_UNKNOWN : uncompressedSize;
154 	fs::write(handle, _uncompressedSize);
155 
156 	u32 nChunks = chunks.size();
157 	fs::write(handle, nChunks);
158 
159 	u32 _comp;
160 	switch(comp) {
161 		case File::None: _comp = SAV_COMP_NONE; break;
162 		case File::ImplodeCrypt: _comp = SAV_COMP_IMPLODE; break;
163 		case File::Deflate: _comp = SAV_COMP_DEFLATE; break;
164 		case File::Unknown: _comp = (u32)-1; break;
165 	}
166 	fs::write(handle, _comp);
167 
168 	for(File::ChunkList::const_iterator chunk = chunks.begin(); chunk != chunks.end(); ++chunk) {
169 
170 		u32 chunkSize = chunk->size;
171 		fs::write(handle, chunkSize);
172 
173 		u32 chunkOffset = chunk->offset;
174 		fs::write(handle, chunkOffset);
175 
176 	}
177 
178 }
179 
loadData(std::istream & handle,size_t & size,const std::string & name) const180 char * SaveBlock::File::loadData(std::istream & handle, size_t & size, const std::string & name) const {
181 
182 	LogDebug("Loading " << name << ' ' << storedSize << "b in " << chunks.size() << " chunks, "
183 	         << compressionName() << " -> " << (int)uncompressedSize << "b");
184 
185 	char * buf = (char*)malloc(storedSize);
186 	char * p = buf;
187 
188 	for(File::ChunkList::const_iterator chunk = chunks.begin();
189 	    chunk != chunks.end(); ++chunk) {
190 		handle.seekg(chunk->offset + 4);
191 		handle.read(p, chunk->size);
192 		p += chunk->size;
193 	}
194 
195 	arx_assert(p == buf + storedSize);
196 
197 	switch(comp) {
198 
199 		case File::None: {
200 			arx_assert(uncompressedSize == storedSize);
201 			size = uncompressedSize;
202 			return buf;
203 		}
204 
205 		case File::ImplodeCrypt: {
206 			unsigned char * crypt = (unsigned char *)buf;
207 			for(size_t i = 0; i < storedSize; i += 2) {
208 				crypt[i] = ~crypt[i];
209 			}
210 			char * uncompressed = blastMemAlloc(buf, storedSize, size);
211 			free(buf);
212 			if(!uncompressed) {
213 				LogError << "Error decompressing imploded " << name;
214 				return NULL;
215 			}
216 			arx_assert(uncompressedSize == (size_t)-1 || size == uncompressedSize);
217 			return uncompressed;
218 		}
219 
220 		case File::Deflate: {
221 			arx_assert(uncompressedSize != (size_t)-1);
222 			uLongf decompressedSize = uncompressedSize;
223 			char * uncompressed = (char*)malloc(uncompressedSize);
224 			int ret = uncompress((Bytef*)uncompressed, &decompressedSize, (const Bytef*)buf, storedSize);
225 			if(ret != Z_OK) {
226 				LogError << "Error decompressing deflated " << name << ": " << zError(ret) << " (" << ret << ')';
227 				free(buf);
228 				free(uncompressed);
229 				size = 0;
230 				return NULL;
231 			}
232 			if(decompressedSize != uncompressedSize) {
233 				LogError << "Unexpedect uncompressed size " << decompressedSize << " while loading "
234 				         << name << ", expected " << uncompressedSize;
235 			}
236 			size = decompressedSize;
237 			free(buf);
238 			return uncompressed;
239 		}
240 
241 		default: {
242 			LogError << "Error decompressing " << name << ": unknown format";
243 			free(buf);
244 			size = 0;
245 			return NULL;
246 		}
247 
248 	}
249 }
250 
SaveBlock(const fs::path & _savefile)251 SaveBlock::SaveBlock(const fs::path & _savefile) : savefile(_savefile), totalSize(0), usedSize(0), chunkCount(0) { }
252 
~SaveBlock()253 SaveBlock::~SaveBlock() { }
254 
loadFileTable()255 bool SaveBlock::loadFileTable() {
256 
257 	handle.seekg(0);
258 
259 	u32 fatOffset;
260 	if(fs::read(handle, fatOffset).fail()) {
261 		return false;
262 	}
263 	if(handle.seekg(fatOffset + 4).fail()) {
264 		LogError << "Cannot seek to FAT";
265 		return false;
266 	}
267 	totalSize = fatOffset;
268 
269 	u32 version;
270 	if(fs::read(handle, version).fail()) {
271 		return false;
272 	}
273 	if(version != SAV_VERSION_DEFLATE && version != SAV_VERSION_RELEASE && version != SAV_VERSION_NOEXT) {
274 		LogWarning << "Unexpected savegame version: " << (version >> 16) << '.' << (version & 0xffff) << " for " << savefile;
275 	}
276 
277 	u32 nFiles;
278 	if(fs::read(handle, nFiles).fail()) {
279 		return false;
280 	}
281 	nFiles = (version == SAV_VERSION_OLD) ? nFiles - 1 : nFiles;
282 	size_t hashMapSize = 1;
283 	while(hashMapSize < nFiles) {
284 		hashMapSize <<= 1;
285 	}
286 	if(nFiles > (hashMapSize * 3) / 4) {
287 		hashMapSize <<= 1;
288 	}
289 	files.rehash(hashMapSize);
290 
291 	if(version == SAV_VERSION_OLD) {
292 		char c;
293 		do {
294 			c = static_cast<char>(handle.get());
295 		} while(c != '\0' && handle.good());
296 		File dummy;
297 		if(!dummy.loadOffsets(handle, version)) {
298 			return false;
299 		}
300 	}
301 
302 	usedSize = 0;
303 	chunkCount = 0;
304 
305 	for(u32 i = 0; i < nFiles; i++) {
306 
307 		// Read the file name.
308 		string name;
309 		if(fs::read(handle, name).fail()) {
310 			return false;
311 		}
312 		if(version < SAV_VERSION_NOEXT) {
313 			boost::to_lower(name);
314 			if(name.size() > 4 && !name.compare(name.size() - 4, 4, ".sav", 4)) {
315 				name.resize(name.size() - 4);
316 			}
317 		}
318 
319 		File & file = files[name];
320 
321 		if(!file.loadOffsets(handle, version)) {
322 			return false;
323 		}
324 
325 		usedSize += file.storedSize, chunkCount += file.chunks.size();
326 	}
327 
328 	return true;
329 }
330 
writeFileTable(const std::string & important)331 void SaveBlock::writeFileTable(const std::string & important) {
332 
333 	LogDebug("writeFileTable " << savefile);
334 
335 	u32 fatOffset = totalSize;
336 	handle.seekp(fatOffset + 4);
337 
338 	fs::write(handle, SAV_VERSION_NOEXT);
339 
340 	u32 nFiles = files.size();
341 	fs::write(handle, nFiles);
342 
343 	Files::const_iterator ifile = files.find(important);
344 	if(ifile != files.end()) {
345 		ifile->second.writeEntry(handle, ifile->first);
346 	}
347 
348 	for(Files::const_iterator file = files.begin(); file != files.end(); ++file) {
349 		if(file != ifile) {
350 			file->second.writeEntry(handle, file->first);
351 		}
352 	}
353 
354 	handle.seekp(0);
355 	fs::write(handle, fatOffset);
356 
357 }
358 
open(bool writable)359 bool SaveBlock::open(bool writable) {
360 
361 	LogDebug("opening savefile " << savefile << " witable=" << writable);
362 
363 	fs::fstream::openmode mode = fs::fstream::in | fs::fstream::binary | fs::fstream::ate;
364 	if(writable) {
365 		mode |= fs::fstream::out;
366 	}
367 
368 	handle.clear();
369 	handle.open(savefile, mode);
370 	if(!handle.is_open()) {
371 		handle.clear();
372 		if(writable) {
373 			handle.open(savefile, mode | fs::fstream::trunc);
374 		}
375 		if(!handle.is_open()) {
376 			LogError << "Could not open " << savefile << " for "
377 			         << (writable ? "reading/writing" : "reading");
378 			return false;
379 		}
380 	}
381 
382 	if(handle.tellg() > 0 && !loadFileTable()) {
383 		LogError << "Broken save file";
384 		return false;
385 	}
386 
387 	return true;
388 }
389 
flush(const string & important)390 bool SaveBlock::flush(const string & important) {
391 
392 	arx_assert_msg(important.find_first_of(BADSAVCHAR) == string::npos,
393 	               "bad save filename: \"%s\"", important.c_str());
394 
395 	if((usedSize * 2 < totalSize || chunkCount > (files.size() * 4 / 3))) {
396 		defragment();
397 	}
398 
399 	writeFileTable(important);
400 
401 	handle.flush();
402 
403 	return handle.good();
404 }
405 
defragment()406 bool SaveBlock::defragment() {
407 
408 	LogDebug("defragmenting " << savefile << " save: using " << usedSize << " / " << totalSize
409 	         << " b for " << files.size() << " files in " << chunkCount << " chunks");
410 
411 	fs::path tempFileName = savefile;
412 	int i = 0;
413 
414 	do {
415 		std::ostringstream oss;
416 		oss << "defrag" << i++;
417 		tempFileName.set_ext(oss.str());
418 	} while(fs::exists(tempFileName));
419 
420 	fs::ofstream tempFile(tempFileName, fs::fstream::out | fs::fstream::binary | fs::fstream::trunc);
421 	if(!tempFile.is_open()) {
422 		return false;
423 	}
424 
425 	totalSize = 0;
426 	tempFile.seekp(4);
427 
428 	for(Files::iterator file = files.begin(); file != files.end(); ++file) {
429 
430 		if(file->second.storedSize == 0) {
431 			continue;
432 		}
433 
434 		char * buf = new char[file->second.storedSize];
435 		char * p = buf;
436 
437 		for(File::ChunkList::iterator chunk = file->second.chunks.begin();
438 		    chunk != file->second.chunks.end(); ++chunk) {
439 			handle.seekg(chunk->offset + 4);
440 			handle.read(p, chunk->size);
441 			p += chunk->size;
442 		}
443 
444 		arx_assert(p == buf + file->second.storedSize);
445 
446 		tempFile.write(buf, file->second.storedSize);
447 
448 		file->second.chunks.resize(1);
449 		file->second.chunks.front().offset = totalSize;
450 		file->second.chunks.front().size = file->second.storedSize;
451 
452 		delete[] buf;
453 
454 		totalSize += file->second.storedSize;
455 	}
456 
457 	usedSize = totalSize, chunkCount = files.size();
458 
459 	if(tempFile.fail()) {
460 		fs::remove(tempFileName);
461 		handle.close(), files.clear();
462 		LogWarning << "Defragmenting failed: " << tempFileName;
463 		return false;
464 	}
465 
466 	tempFile.flush(), tempFile.close(), handle.close();
467 
468 	if(!fs::rename(tempFileName, savefile, true)) {
469 		LogWarning << "Failed to move defragmented savegame " << tempFileName << " to " << savefile;
470 		return false;
471 	}
472 
473 	handle.open(savefile, fs::fstream::in | fs::fstream::out | fs::fstream::binary);
474 	return handle.is_open();
475 }
476 
save(const string & name,const char * data,size_t size)477 bool SaveBlock::save(const string & name, const char * data, size_t size) {
478 
479 	if(!handle) {
480 		return false;
481 	}
482 
483 	arx_assert_msg(name.find_first_of(BADSAVCHAR) == string::npos,
484 	               "bad save filename: \"%s\"", name.c_str());
485 
486 	File * file = &files[name];
487 
488 	file->uncompressedSize = size;
489 
490 	if(size == 0) {
491 		file->comp = File::None;
492 		file->storedSize = 0;
493 		return true;
494 	}
495 
496 	uLongf compressedSize = size - 1;
497 	char * compressed = new char[compressedSize];
498 	const char * p;
499 	if(compress2((Bytef*)compressed, &compressedSize, (const Bytef*)data, size, 1) == Z_OK) {
500 		file->comp = File::Deflate;
501 		file->storedSize = compressedSize;
502 		p = compressed;
503 	} else {
504 		file->comp = File::None;
505 		file->storedSize = size;
506 		p = data;
507 	}
508 
509 	LogDebug("saving " << name << " " << file->uncompressedSize << " " << file->storedSize);
510 
511 	size_t remaining = file->storedSize;
512 
513 	for(File::ChunkList::iterator chunk = file->chunks.begin();
514 	    chunk != file->chunks.end(); ++chunk) {
515 
516 		handle.seekp(chunk->offset + 4);
517 
518 		if(chunk->size > remaining) {
519 			usedSize -= chunk->size - remaining;
520 			chunk->size = remaining;
521 		}
522 
523 		handle.write(p, chunk->size);
524 		p += chunk->size;
525 		remaining -= chunk->size;
526 
527 		if(remaining == 0) {
528 			file->chunks.erase(++chunk, file->chunks.end());
529 			delete[] compressed;
530 			return true;
531 		}
532 	}
533 
534 	file->chunks.push_back(File::Chunk(remaining, totalSize));
535 	handle.seekp(totalSize + 4);
536 	handle.write(p, remaining);
537 	totalSize += remaining, usedSize += remaining, chunkCount++;
538 
539 	delete[] compressed;
540 
541 	return !handle.fail();
542 }
543 
load(const string & name,size_t & size)544 char * SaveBlock::load(const string & name, size_t & size) {
545 
546 	arx_assert_msg(name.find_first_of(BADSAVCHAR) == string::npos,
547 	               "bad save filename: \"%s\"", name.c_str());
548 
549 	Files::const_iterator file = files.find(name);
550 
551 	return (file == files.end()) ? NULL : file->second.loadData(handle, size, name);
552 }
553 
hasFile(const string & name) const554 bool SaveBlock::hasFile(const string & name) const {
555 	arx_assert_msg(name.find_first_of(BADSAVCHAR) == string::npos,
556 	               "bad save filename: \"%s\"", name.c_str());
557 	return (files.find(name) != files.end());
558 }
559 
getFiles() const560 vector<string> SaveBlock::getFiles() const {
561 
562 	vector<string> result;
563 
564 	for(Files::const_iterator file = files.begin(); file != files.end(); ++file) {
565 		result.push_back(file->first);
566 	}
567 
568 	return result;
569 }
570 
load(const fs::path & savefile,const std::string & filename,size_t & size)571 char * SaveBlock::load(const fs::path & savefile, const std::string & filename, size_t & size) {
572 
573 	arx_assert_msg(filename.find_first_of(BADSAVCHAR) == string::npos,
574 	               "bad save filename: \"%s\"", filename.c_str());
575 
576 	LogDebug("reading savefile " << savefile);
577 
578 	size = 0;
579 
580 	fs::ifstream handle(savefile, fs::fstream::in | fs::fstream::binary);
581 	if(!handle.is_open()) {
582 		LogWarning << "Cannot open save file " << savefile;
583 		return NULL;
584 	}
585 
586 	u32 fatOffset;
587 	if(fs::read(handle, fatOffset).fail()) {
588 		return NULL;
589 	}
590 	if(handle.seekg(fatOffset + 4).fail()) {
591 		LogError << "Cannot seek to FAT";
592 		return NULL;
593 	}
594 
595 	u32 version;
596 	if(fs::read(handle, version).fail()) {
597 		return NULL;
598 	}
599 	if(version != SAV_VERSION_DEFLATE && version != SAV_VERSION_RELEASE && version != SAV_VERSION_NOEXT) {
600 		LogWarning << "Unexpected savegame version: " << version << " for " << savefile;
601 	}
602 
603 	u32 nFiles;
604 	if(fs::read(handle, nFiles).fail()) {
605 		return NULL;
606 	}
607 
608 	File file;
609 
610 	for(u32 i = 0; i < nFiles; i++) {
611 
612 		// Read the file name.
613 		string name;
614 		if(fs::read(handle, name).fail()) {
615 			return NULL;
616 		}
617 		if(version < SAV_VERSION_NOEXT) {
618 			boost::to_lower(name);
619 			if(name.size() > 4 && !name.compare(name.size() - 4, 4, ".sav", 4)) {
620 				name.resize(name.size() - 4);
621 			}
622 		}
623 
624 		if(!file.loadOffsets(handle, version)) {
625 			return NULL;
626 		}
627 
628 		if(!i && version == SAV_VERSION_OLD) {
629 			continue;
630 		}
631 
632 		if(name != filename) {
633 			file.chunks.clear();
634 			continue;
635 		}
636 
637 		return file.loadData(handle, size, name);
638 	}
639 
640 	return NULL;
641 }
642