301 fseek(f, 0, SEEK_SET); |
310 fseek(f, 0, SEEK_SET); |
302 } |
311 } |
303 return f; |
312 return f; |
304 } |
313 } |
305 |
314 |
306 FILE *FioTarFileList(const char *tar, const char *mode, size_t *filesize, FioTarFileListCallback *callback, void *userdata) |
315 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize) |
|
316 { |
|
317 FILE *f = fopen(entry->tar->filename, "rb"); |
|
318 assert(f != NULL); |
|
319 |
|
320 fseek(f, entry->position, SEEK_SET); |
|
321 if (filesize != NULL) *filesize = entry->size; |
|
322 return f; |
|
323 } |
|
324 |
|
325 /** Opens OpenTTD files somewhere in a personal or global directory */ |
|
326 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize) |
|
327 { |
|
328 FILE *f = NULL; |
|
329 Searchpath sp; |
|
330 |
|
331 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY); |
|
332 |
|
333 FOR_ALL_SEARCHPATHS(sp) { |
|
334 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize); |
|
335 if (f != NULL || subdir == NO_DIRECTORY) break; |
|
336 } |
|
337 |
|
338 /* We can only use .tar in case of data-dir, and read-mode */ |
|
339 if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') { |
|
340 /* Filenames in tars are always forced to be lowercase */ |
|
341 char *lcfilename = strdup(filename); |
|
342 strtolower(lcfilename); |
|
343 TarFileList::iterator it = _tar_filelist.find(lcfilename); |
|
344 free(lcfilename); |
|
345 if (it != _tar_filelist.end()) { |
|
346 f = FioFOpenFileTar(&((*it).second), filesize); |
|
347 } |
|
348 } |
|
349 |
|
350 return f; |
|
351 } |
|
352 |
|
353 /** |
|
354 * Create a directory with the given name |
|
355 * @param name the new name of the directory |
|
356 */ |
|
357 void FioCreateDirectory(const char *name) |
|
358 { |
|
359 #if defined(WIN32) || defined(WINCE) |
|
360 CreateDirectory(OTTD2FS(name), NULL); |
|
361 #elif defined(OS2) && !defined(__INNOTEK_LIBC__) |
|
362 mkdir(OTTD2FS(name)); |
|
363 #else |
|
364 mkdir(OTTD2FS(name), 0755); |
|
365 #endif |
|
366 } |
|
367 |
|
368 /** |
|
369 * Appends, if necessary, the path separator character to the end of the string. |
|
370 * It does not add the path separator to zero-sized strings. |
|
371 * @param buf string to append the separator to |
|
372 * @param buflen the length of the buf |
|
373 */ |
|
374 void AppendPathSeparator(char *buf, size_t buflen) |
|
375 { |
|
376 size_t s = strlen(buf); |
|
377 |
|
378 /* Length of string + path separator + '\0' */ |
|
379 if (s != 0 && buf[s - 1] != PATHSEPCHAR && s + 2 < buflen) { |
|
380 buf[s] = PATHSEPCHAR; |
|
381 buf[s + 1] = '\0'; |
|
382 } |
|
383 } |
|
384 |
|
385 /** |
|
386 * Allocates and files a variable with the full path |
|
387 * based on the given directory. |
|
388 * @param dir the directory to base the path on |
|
389 * @return the malloced full path |
|
390 */ |
|
391 char *BuildWithFullPath(const char *dir) |
|
392 { |
|
393 char *dest = MallocT<char>(MAX_PATH); |
|
394 ttd_strlcpy(dest, dir, MAX_PATH); |
|
395 |
|
396 /* Check if absolute or relative path */ |
|
397 const char *s = strchr(dest, PATHSEPCHAR); |
|
398 |
|
399 /* Add absolute path */ |
|
400 if (s == NULL || dest != s) { |
|
401 getcwd(dest, MAX_PATH); |
|
402 AppendPathSeparator(dest, MAX_PATH); |
|
403 ttd_strlcat(dest, dir, MAX_PATH); |
|
404 } |
|
405 AppendPathSeparator(dest, MAX_PATH); |
|
406 |
|
407 return dest; |
|
408 } |
|
409 |
|
410 static bool TarListAddFile(const char *filename) |
307 { |
411 { |
308 /* The TAR-header, repeated for every file */ |
412 /* The TAR-header, repeated for every file */ |
309 typedef struct TarHeader { |
413 typedef struct TarHeader { |
310 char name[100]; ///< Name of the file |
414 char name[100]; ///< Name of the file |
311 char mode[8]; |
415 char mode[8]; |
325 char prefix[155]; ///< Path of the file |
429 char prefix[155]; ///< Path of the file |
326 |
430 |
327 char unused[12]; |
431 char unused[12]; |
328 } TarHeader; |
432 } TarHeader; |
329 |
433 |
330 assert(mode[0] == 'r'); // Only reading is supported |
434 /* Check if we already seen this file */ |
331 assert(callback != NULL); // We need a callback, else this function doens't do much |
435 TarList::iterator it = _tar_list.find(filename); |
332 |
436 if (it != _tar_list.end()) return false; |
333 #if defined(WIN32) && defined(UNICODE) |
437 |
334 /* fopen is implemented as a define with ellipses for |
438 FILE *f = fopen(filename, "rb"); |
335 * Unicode support (prepend an L). As we are not sending |
|
336 * a string, but a variable, it 'renames' the variable, |
|
337 * so make that variable to makes it compile happily */ |
|
338 wchar_t Lmode[5]; |
|
339 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode)); |
|
340 #endif |
|
341 |
|
342 FILE *f = fopen(tar, mode); |
|
343 assert(f != NULL); |
439 assert(f != NULL); |
|
440 |
|
441 TarListEntry *tar_entry = MallocT<TarListEntry>(1); |
|
442 tar_entry->filename = strdup(filename); |
|
443 _tar_list.insert(TarList::value_type(filename, tar_entry)); |
344 |
444 |
345 TarHeader th; |
445 TarHeader th; |
346 char buf[sizeof(th.name) + 1], *end; |
446 char buf[sizeof(th.name) + 1], *end; |
347 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1]; |
447 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1]; |
|
448 int num = 0, pos = 0; |
|
449 |
|
450 /* Make a char of 512 empty bytes */ |
|
451 char empty[512]; |
|
452 memset(&empty[0], 0, sizeof(empty)); |
348 |
453 |
349 while (!feof(f)) { |
454 while (!feof(f)) { |
350 /* Read the header and make sure it is a valid one */ |
|
351 fread(&th, 1, 512, f); |
455 fread(&th, 1, 512, f); |
352 /* 'ustar' is the new format, '\0' is the old format */ |
456 pos += 512; |
353 if (th.magic[0] != '\0' && strncmp(th.magic, "ustar", 5) != 0) return NULL; |
457 |
|
458 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */ |
|
459 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) { |
|
460 /* If we have only zeros in the block, it can be an end-of-file indicator */ |
|
461 if (memcmp(&th, &empty[0], 512) == 0) continue; |
|
462 |
|
463 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename); |
|
464 return false; |
|
465 } |
354 |
466 |
355 name[0] = '\0'; |
467 name[0] = '\0'; |
356 int len = 0; |
468 int len = 0; |
357 |
469 |
358 /* The prefix contains the directory-name */ |
470 /* The prefix contains the directory-name */ |
374 int skip = strtol(buf, &end, 8); |
486 int skip = strtol(buf, &end, 8); |
375 |
487 |
376 /* 0 byte sized files can be skipped (dirs, symlinks, ..) */ |
488 /* 0 byte sized files can be skipped (dirs, symlinks, ..) */ |
377 if (skip == 0) continue; |
489 if (skip == 0) continue; |
378 |
490 |
379 /* Check in the callback if this is the file we want */ |
491 /* Store this entry in the list */ |
380 if (callback(name, skip, userdata)) { |
492 TarFileListEntry entry; |
381 if (filesize != NULL) *filesize = skip; |
493 entry.tar = tar_entry; |
382 return f; |
494 entry.size = skip; |
383 } |
495 entry.position = pos; |
|
496 /* Force lowercase */ |
|
497 strtolower(name); |
|
498 |
|
499 /* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */ |
|
500 #if (PATHSEPCHAR != '/') |
|
501 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR; |
|
502 #endif |
|
503 |
|
504 DEBUG(misc, 6, "Found file in tar: %s (%d bytes, %d offset)", name, skip, pos); |
|
505 if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++; |
384 |
506 |
385 /* Skip to the next block.. */ |
507 /* Skip to the next block.. */ |
386 fseek(f, ALIGN(skip, 512), SEEK_CUR); |
508 skip = ALIGN(skip, 512); |
387 } |
509 fseek(f, skip, SEEK_CUR); |
388 |
510 pos += skip; |
|
511 } |
|
512 |
|
513 DEBUG(misc, 1, "Found tar '%s' with %d new files", filename, num); |
389 fclose(f); |
514 fclose(f); |
390 return NULL; |
|
391 } |
|
392 |
|
393 bool FioFOpenFileTarFileListCallback(const char *filename, int size, void *search_filename) |
|
394 { |
|
395 return strcasecmp(filename, (const char *)search_filename) == 0; |
|
396 } |
|
397 |
|
398 FILE *FioFOpenFileTar(const char *filename, const char *tar_filename, size_t *filesize) |
|
399 { |
|
400 return FioTarFileList(tar_filename, "rb", filesize, FioFOpenFileTarFileListCallback, (void *)filename); |
|
401 } |
|
402 |
|
403 /** Opens OpenTTD files somewhere in a personal or global directory */ |
|
404 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize) |
|
405 { |
|
406 FILE *f = NULL; |
|
407 Searchpath sp; |
|
408 |
|
409 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY); |
|
410 |
|
411 FOR_ALL_SEARCHPATHS(sp) { |
|
412 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize); |
|
413 if (f != NULL || subdir == NO_DIRECTORY) break; |
|
414 } |
|
415 /* We can only use .tar in case of data-dir, and read-mode */ |
|
416 if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') { |
|
417 const char *tar; |
|
418 FOR_ALL_TARS(tar) { |
|
419 f = FioFOpenFileTar(filename, tar, filesize); |
|
420 if (f != NULL) break; |
|
421 } |
|
422 } |
|
423 |
|
424 return f; |
|
425 } |
|
426 |
|
427 /** |
|
428 * Create a directory with the given name |
|
429 * @param name the new name of the directory |
|
430 */ |
|
431 void FioCreateDirectory(const char *name) |
|
432 { |
|
433 #if defined(WIN32) || defined(WINCE) |
|
434 CreateDirectory(OTTD2FS(name), NULL); |
|
435 #elif defined(OS2) && !defined(__INNOTEK_LIBC__) |
|
436 mkdir(OTTD2FS(name)); |
|
437 #else |
|
438 mkdir(OTTD2FS(name), 0755); |
|
439 #endif |
|
440 } |
|
441 |
|
442 /** |
|
443 * Appends, if necessary, the path separator character to the end of the string. |
|
444 * It does not add the path separator to zero-sized strings. |
|
445 * @param buf string to append the separator to |
|
446 * @param buflen the length of the buf |
|
447 */ |
|
448 void AppendPathSeparator(char *buf, size_t buflen) |
|
449 { |
|
450 size_t s = strlen(buf); |
|
451 |
|
452 /* Length of string + path separator + '\0' */ |
|
453 if (s != 0 && buf[s - 1] != PATHSEPCHAR && s + 2 < buflen) { |
|
454 buf[s] = PATHSEPCHAR; |
|
455 buf[s + 1] = '\0'; |
|
456 } |
|
457 } |
|
458 |
|
459 /** |
|
460 * Allocates and files a variable with the full path |
|
461 * based on the given directory. |
|
462 * @param dir the directory to base the path on |
|
463 * @return the malloced full path |
|
464 */ |
|
465 char *BuildWithFullPath(const char *dir) |
|
466 { |
|
467 char *dest = MallocT<char>(MAX_PATH); |
|
468 ttd_strlcpy(dest, dir, MAX_PATH); |
|
469 |
|
470 /* Check if absolute or relative path */ |
|
471 const char *s = strchr(dest, PATHSEPCHAR); |
|
472 |
|
473 /* Add absolute path */ |
|
474 if (s == NULL || dest != s) { |
|
475 getcwd(dest, MAX_PATH); |
|
476 AppendPathSeparator(dest, MAX_PATH); |
|
477 ttd_strlcat(dest, dir, MAX_PATH); |
|
478 } |
|
479 AppendPathSeparator(dest, MAX_PATH); |
|
480 |
|
481 return dest; |
|
482 } |
|
483 |
|
484 static bool TarListAddFile(const char *filename) |
|
485 { |
|
486 /* See if we already have a tar by that name; useless to have double entries in our list */ |
|
487 const char *tar; |
|
488 FOR_ALL_TARS(tar) { |
|
489 if (strcmp(tar, filename) == 0) return false; |
|
490 } |
|
491 |
|
492 DEBUG(misc, 1, "Found tar: %s", filename); |
|
493 _tar_list.push_back(strdup(filename)); |
|
494 |
515 |
495 return true; |
516 return true; |
496 } |
517 } |
497 |
518 |
498 static int ScanPathForTarFiles(const char *path, int basepath_length) |
519 static int ScanPathForTarFiles(const char *path, int basepath_length) |