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