1 /* ScummVM Tools
2  *
3  * ScummVM Tools is the legal property of its developers, whose
4  * names are too numerous to list here. Please refer to the
5  * COPYRIGHT file distributed with this source distribution.
6  *
7  * Additionally this file is based on the ScummVM source code.
8  * Copyright information for the ScummVM source code is
9  * available in the COPYRIGHT file of the ScummVM source
10  * distribution.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  */
26 
27 #include "file.h"
28 #include "common/str.h"
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <assert.h>
32 #include <deque>
33 #include <algorithm>
34 #include <sys/stat.h>   // for stat()
35 #include <sys/types.h>
36 #ifndef _MSC_VER
37 #include <unistd.h>	// for unlink()
38 #else
39 
40 // Add a definition for S_IFDIR for MSVC
41 #ifndef S_ISDIR
42 #ifndef S_IFDIR
43 #ifndef _S_IFDIR
44 #define S_IFDIR		(-1)
45 #else
46 #define S_IFDIR		_S_IFDIR
47 #endif
48 #endif
49 #define S_ISDIR(m)	(((m)&S_IFDIR)==S_IFDIR)
50 #endif
51 
52 #endif
53 
54 namespace Common {
55 
56 // Filenname implementation
Filename()57 Filename::Filename() {
58 }
Filename(const char * path)59 Filename::Filename(const char *path) : _path(path) {
60 	postInitWithString();
61 }
Filename(const std::string & path)62 Filename::Filename(const std::string &path) : _path(path) {
63 	postInitWithString();
64 }
65 
Filename(const Common::String & path)66 Filename::Filename(const Common::String &path) : _path(path.c_str()) {
67 	postInitWithString();
68 }
69 
postInitWithString()70 void Filename::postInitWithString() {
71 	// If this is a directory append '/' at the end if needed.
72 	if (!_path.empty() && _path[_path.size() - 1] != '/' && _path[_path.size() - 1] != '\\' && isDirectory(_path.c_str()))
73 		_path += '/';
74 }
75 
Filename(const Filename & filename)76 Filename::Filename(const Filename& filename) : _path(filename._path) {
77 }
78 
operator =(const Filename & filename)79 Filename& Filename::operator=(const Filename& filename) {
80 	_path = filename._path;
81 	return *this;
82 }
83 
setFullPath(const std::string & path)84 void Filename::setFullPath(const std::string &path) {
85 	_path = path;
86 	// If this is a directory append '/' at the end if needed.
87 	if (!_path.empty() && _path[_path.size() - 1] != '/' && _path[_path.size() - 1] != '\\' && isDirectory(_path.c_str()))
88 		_path += '/';
89 }
90 
setFullName(const std::string & newname)91 void Filename::setFullName(const std::string &newname) {
92 	_path = getPath() + newname;
93 }
94 
addExtension(const std::string & ext)95 void Filename::addExtension(const std::string &ext) {
96 	_path += ext;
97 }
98 
setExtension(const std::string & ext)99 void Filename::setExtension(const std::string &ext) {
100 	// FIXME: quick and dirty hack. But then so is this whole class...
101 	std::string path = getPath();
102 	std::string name = getName();
103 	_path = path + name + ext;
104 }
105 
equals(const Filename & other) const106 bool Filename::equals(const Filename &other) const {
107 #ifdef _WIN32
108 	// On Windows paths are case-insensitive
109 	return scumm_stricmp(_path.c_str(), other._path.c_str()) == 0;
110 #else
111 	return _path == other._path;
112 #endif
113 }
114 
empty() const115 bool Filename::empty() const {
116 	return _path.empty();
117 }
118 
directory() const119 bool Filename::directory() const {
120 	return getFullName().size() == 0;
121 }
122 
exists() const123 bool Filename::exists() const {
124 	// This fails if we don't have permission to read the file
125 	// but in most cases, that's the same thing for us.
126 	FILE *f = fopen(_path.c_str(), "r");
127 	if (!f) {
128 		std::string fixedPath = fixPathCase(_path);
129 		f = fopen(_path.c_str(), "r");
130 	}
131 	if (f) {
132 		fclose(f);
133 		return true;
134 	}
135 	return false;
136 }
137 
hasExtension(std::string ext) const138 bool Filename::hasExtension(std::string ext) const {
139 	size_t dot = _path.rfind('.');
140 	if (dot == std::string::npos)
141 		return false;
142 
143 	// Check that dot position is less than /, since some
144 	// directories contain ., like /home/.data/file
145 	size_t slash = _path.rfind('/');
146 	if (slash != std::string::npos)
147 		if (slash > dot)
148 			return false;
149 
150 	slash = _path.rfind('\\');
151 	if (slash != std::string::npos)
152 		if (slash > dot)
153 			return false;
154 
155 	// We compare extensions, skip any dots
156 	if (_path[dot] == '.')
157 		dot++;
158 	if (ext[0] == '.')
159 		ext = ext.substr(1);
160 
161 	std::string tmp = _path.substr(dot);
162 	// On Windows paths are case-insensitive
163 	return scumm_stricmp(tmp.c_str(), ext.c_str()) == 0;
164 }
165 
getFullPath() const166 std::string Filename::getFullPath() const {
167 	return _path;
168 }
169 
getFullName() const170 std::string Filename::getFullName() const {
171 	size_t slash = _path.rfind('/');
172 	if (slash == std::string::npos)
173 		slash = _path.rfind('\\');
174 
175 	if (slash == std::string::npos)
176 		return _path;
177 
178 	return _path.substr(slash + 1);
179 }
180 
getName() const181 std::string Filename::getName() const {
182 	size_t slash = _path.rfind('/');
183 	size_t dot = _path.rfind('.');
184 	if (slash == std::string::npos)
185 		slash = _path.rfind('\\');
186 
187 	if (dot == std::string::npos)
188 		dot = _path.size();
189 
190 	if (slash == std::string::npos)
191 		return _path.substr(0, dot);
192 
193 	if (dot < slash)
194 		dot = _path.size();
195 
196 	return _path.substr(slash + 1, dot - slash - 1);
197 }
198 
getExtension() const199 std::string Filename::getExtension() const {
200 	size_t slash = _path.rfind('/');
201 	size_t dot = _path.rfind('.');
202 	if (slash == std::string::npos)
203 		slash = _path.rfind('\\');
204 
205 	if (slash == std::string::npos)
206 		slash = 0;
207 
208 	if (dot == std::string::npos)
209 		return "";
210 
211 	if (dot < slash)
212 		return "";
213 
214 	return _path.substr(dot + 1);
215 }
216 
getPath() const217 std::string Filename::getPath() const {
218 	size_t slash = _path.rfind('/');
219 	if (slash == std::string::npos)
220 		slash = _path.rfind('\\');
221 
222 	if (slash == std::string::npos)
223 		return "";
224 
225 	return _path.substr(0, slash + 1);
226 }
227 
228 // File interface
229 // While this does massive duplication of the code above, it's required to make sure that
230 // unconverted tools are backwards-compatible
231 
File()232 File::File() {
233 	_file = NULL;
234 	_mode = FILEMODE_READ;
235 	_xormode = 0;
236 }
237 
File(const Filename & filepath,const char * mode)238 File::File(const Filename &filepath, const char *mode) {
239 	_file = NULL;
240 	_mode = FILEMODE_READ;
241 	_xormode = 0;
242 
243 	open(filepath, mode);
244 }
245 
~File()246 File::~File() {
247 	close();
248 }
249 
open(const Filename & filepath,const char * mode)250 void File::open(const Filename &filepath, const char *mode) {
251 
252 	// Clean up previously opened file
253 	close();
254 
255 	_file = fopen(filepath.getFullPath().c_str(), mode);
256 	if (!_file) {
257 		std::string fixedPath = fixPathCase(filepath.getFullPath());
258 		_file = fopen(fixedPath.c_str(), mode);
259 	}
260 
261 	FileMode m = FILEMODE_READ;
262 	do {
263 		switch(*mode) {
264 		case 'w': m = FILEMODE_WRITE; break;
265 		case 'r': m = FILEMODE_READ; break;
266 		case 'b': m = FileMode(m | FILEMODE_BINARY); break;
267 		case '+': m = FileMode(m | FILEMODE_READ | FILEMODE_WRITE); break;
268 		default: throw FileException(std::string("Unsupported FileMode ") + mode);
269 		}
270 	} while (*++mode);
271 	_mode = m;
272 
273 	_name = filepath;
274 	_xormode = 0;
275 
276 	if (!_file)
277 		throw FileException("Could not open file " + filepath.getFullPath());
278 }
279 
close()280 void File::close() {
281 	if (_file)
282 		fclose(_file);
283 	_file = NULL;
284 }
285 
setXorMode(uint8 xormode)286 void File::setXorMode(uint8 xormode) {
287 	_xormode = xormode;
288 }
289 
readChar()290 int File::readChar() {
291 	if (!_file)
292 		throw FileException("File is not open");
293 	if ((_mode & FILEMODE_READ) == 0)
294 		throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")");
295 
296 	int u8 = fgetc(_file);
297 	if (u8 == EOF)
298 		throw FileException("Read beyond the end of file (" + _name.getFullPath() + ")");
299 	u8 ^= _xormode;
300 	return u8;
301 }
302 
readByte()303 uint8 File::readByte() {
304 	int u8 = readChar();
305 	return (uint8)u8;
306 }
307 
readUint16BE()308 uint16 File::readUint16BE() {
309 	uint16 ret = 0;
310 	ret |= uint16(readByte() << 8ul);
311 	ret |= uint16(readByte());
312 	return ret;
313 }
314 
readUint16LE()315 uint16 File::readUint16LE() {
316 	uint16 ret = 0;
317 	ret |= uint16(readByte());
318 	ret |= uint16(readByte() << 8ul);
319 	return ret;
320 }
321 
readUint32BE()322 uint32 File::readUint32BE() {
323 	uint32 ret = 0;
324 	ret |= uint32(readByte() << 24);
325 	ret |= uint32(readByte() << 16);
326 	ret |= uint32(readByte() << 8);
327 	ret |= uint32(readByte());
328 	return ret;
329 }
330 
readUint32LE()331 uint32 File::readUint32LE() {
332 	uint32 ret = 0;
333 	ret |= uint32(readByte());
334 	ret |= uint32(readByte() << 8);
335 	ret |= uint32(readByte() << 16);
336 	ret |= uint32(readByte() << 24);
337 	return ret;
338 }
339 
readSint16BE()340 int16 File::readSint16BE() {
341 	int16 ret = 0;
342 	ret |= int16(readByte() << 8ul);
343 	ret |= int16(readByte());
344 	return ret;
345 }
346 
readSint16LE()347 int16 File::readSint16LE() {
348 	int16 ret = 0;
349 	ret |= int16(readByte());
350 	ret |= int16(readByte() << 8ul);
351 	return ret;
352 }
353 
readSint32BE()354 int32 File::readSint32BE() {
355 	int32 ret = 0;
356 	ret |= int32(readByte() << 24);
357 	ret |= int32(readByte() << 16);
358 	ret |= int32(readByte() << 8);
359 	ret |= int32(readByte());
360 	return ret;
361 }
362 
readSint32LE()363 int32 File::readSint32LE() {
364 	int32 ret = 0;
365 	ret |= int32(readByte());
366 	ret |= int32(readByte() << 8);
367 	ret |= int32(readByte() << 16);
368 	ret |= int32(readByte() << 24);
369 	return ret;
370 }
371 
read_throwsOnError(void * dataPtr,size_t dataSize)372 void File::read_throwsOnError(void *dataPtr, size_t dataSize) {
373 	size_t data_read = read_noThrow(dataPtr, dataSize);
374 	if (data_read != dataSize)
375 		throw FileException("Read beyond the end of file (" + _name.getFullPath() + ")");
376 }
377 
read_noThrow(void * dataPtr,size_t dataSize)378 size_t File::read_noThrow(void *dataPtr, size_t dataSize) {
379 	if (!_file)
380 		throw FileException("File is not open");
381 	if ((_mode & FILEMODE_READ) == 0)
382 		throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")");
383 
384 	return fread(dataPtr, 1, dataSize, _file);
385 }
386 
readString()387 std::string File::readString() {
388 	if (!_file)
389 		throw FileException("File is not open");
390 	if ((_mode & FILEMODE_READ) == 0)
391 		throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")");
392 
393 	std::string s;
394 	try {
395 		char c;
396 		while ((c = readByte())) {
397 			s += c;
398 		}
399 	} catch (FileException &) {
400 		// pass, we reached EOF
401 	}
402 
403 	return s;
404 }
405 
readString(size_t len)406 std::string File::readString(size_t len) {
407 	if (!_file)
408 		throw FileException("File is not open");
409 	if ((_mode & FILEMODE_READ) == 0)
410 		throw FileException("Tried to read from file opened in write mode (" + _name.getFullPath() + ")");
411 
412 	std::string s('\0', len);
413 	std::string::iterator is = s.begin();
414 
415 	char c;
416 	while ((c = readByte())) {
417 		*is = c;
418 	}
419 
420 	return s;
421 }
422 
scanString(char * result)423 void File::scanString(char *result) {
424 	if (!_file)
425 		throw FileException("File is not open");
426 	if ((_mode & FILEMODE_READ) == 0)
427 		throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")");
428 
429 	fscanf(_file, "%s", result);
430 }
431 
writeChar(char i)432 void File::writeChar(char i) {
433 	if (!_file)
434 		throw FileException("File is not open");
435 	if ((_mode & FILEMODE_WRITE) == 0)
436 		throw FileException("Tried to write to a file opened in read mode (" + _name.getFullPath() + ")");
437 
438 	i ^= _xormode;
439 
440 	if (fwrite(&i, 1, 1, _file) != 1)
441 		throw FileException("Could not write to file (" + _name.getFullPath() + ")");
442 }
443 
writeByte(uint8 b)444 void File::writeByte(uint8 b) {
445 	writeChar(b);
446 }
447 
writeUint16BE(uint16 value)448 void File::writeUint16BE(uint16 value) {
449 	writeByte((uint8)(value >> 8));
450 	writeByte((uint8)(value));
451 }
452 
writeUint16LE(uint16 value)453 void File::writeUint16LE(uint16 value) {
454 	writeByte((uint8)(value));
455 	writeByte((uint8)(value >> 8));
456 }
457 
writeUint32BE(uint32 value)458 void File::writeUint32BE(uint32 value) {
459 	writeByte((uint8)(value >> 24));
460 	writeByte((uint8)(value >> 16));
461 	writeByte((uint8)(value >> 8));
462 	writeByte((uint8)(value));
463 }
464 
writeUint32LE(uint32 value)465 void File::writeUint32LE(uint32 value) {
466 	writeByte((uint8)(value));
467 	writeByte((uint8)(value >> 8));
468 	writeByte((uint8)(value >> 16));
469 	writeByte((uint8)(value >> 24));
470 }
471 
write(const void * dataPtr,size_t dataSize)472 size_t File::write(const void *dataPtr, size_t dataSize) {
473 	if (!_file)
474 		throw FileException("File is not open");
475 	if ((_mode & FILEMODE_WRITE) == 0)
476 		throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")");
477 
478 	assert(_xormode == 0);	// FIXME: This method does not work in XOR mode (and probably shouldn't)
479 
480 	size_t data_read = fwrite(dataPtr, 1, dataSize, _file);
481 	if (data_read != dataSize)
482 		throw FileException("Could not write to file (" + _name.getFullPath() + ")");
483 
484 	return data_read;
485 }
486 
print(const char * format,...)487 void File::print(const char *format, ...) {
488 	if (!_file)
489 		throw FileException("File is not open");
490 	if ((_mode & FILEMODE_WRITE) == 0)
491 		throw FileException("Tried to write to file opened in read mode (" + _name.getFullPath() + ")");
492 
493 
494 	va_list va;
495 
496 	va_start(va, format);
497 	vfprintf(_file, format, va);
498 	va_end(va);
499 }
500 
seek(long offset,int origin)501 void File::seek(long offset, int origin) {
502 	if (!_file)
503 		throw FileException("File is not open");
504 
505 	if (fseek(_file, offset, origin) != 0)
506 		throw FileException("Could not seek in file (" + _name.getFullPath() + ")");
507 }
508 
rewind()509 void File::rewind() {
510 	return ::rewind(_file);
511 }
512 
pos() const513 int File::pos() const {
514 	return ftell(_file);
515 }
516 
err() const517 int File::err() const {
518 	return ferror(_file);
519 }
520 
clearErr()521 void File::clearErr() {
522 	clearerr(_file);
523 }
524 
eos() const525 bool File::eos() const {
526 	return feof(_file) != 0;
527 }
528 
size() const529 uint32 File::size() const {
530 	uint32 sz;
531 	uint32 p = ftell(_file);
532 	fseek(_file, 0, SEEK_END);
533 	sz = ftell(_file);
534 	fseek(_file, p, SEEK_SET);
535 	return sz;
536 }
537 
removeFile(const char * path)538 int removeFile(const char *path) {
539 	return unlink(path);
540 }
541 
isDirectory(const char * path)542 bool isDirectory(const char *path) {
543 	struct stat st;
544 	if (stat(path, &st) == 0) {
545 		return S_ISDIR(st.st_mode);
546 	}
547 	// Try to fix the case
548 	std::string fixedPath = fixPathCase(path);
549 	return stat(fixedPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
550 }
551 
fixPathCase(const std::string & originalPath)552 std::string fixPathCase(const std::string& originalPath) {
553 	std::string result = originalPath;
554 	std::deque<std::string> parts;
555 	struct stat st;
556 
557 	// Remove the last part of the path until we get to a path that exists
558 	while (stat(result.c_str(), &st) != 0) {
559 		size_t slash = result.rfind('/', result.size() - 2);
560 		if (slash == std::string::npos)
561 			slash = result.rfind('\\', result.size() - 2);
562 		if (slash == std::string::npos) {
563 			parts.push_back(result);
564 			result.clear();
565 			break;
566 		} else {
567 			parts.push_back(result.substr(slash + 1));
568 			result.erase(slash + 1);
569 			if (slash == 0)
570 				break;
571 		}
572 	}
573 
574 	// Reconstruct the path by changing the case
575 	while (!parts.empty()) {
576 		std::string directory = parts.back();
577 		// Try first original case, then all lower case and finally all upper case
578 		std::string path = result + directory;
579 		if (stat(path.c_str(), &st) == 0) {
580 			result = path;
581 		} else {
582 			std::transform(directory.begin(), directory.end(), directory.begin(), ::tolower);
583 			path = result + directory;
584 			if (stat(path.c_str(), &st) == 0) {
585 				result = path;
586 			} else {
587 				std::transform(directory.begin(), directory.end(), directory.begin(), ::toupper);
588 				path = result + directory;
589 				if (stat(path.c_str(), &st) == 0) {
590 					result = path;
591 				} else {
592 					// Does not exists whatever the case used.
593 					// Add back all the remaining parts and return.
594 					while (!parts.empty()) {
595 						result += parts.back();
596 						parts.pop_back();
597 					}
598 					return result;
599 				}
600 			}
601 		}
602 		parts.pop_back();
603 	}
604 
605 	return result;
606 }
607 
608 } // End of namespace Common
609 
610