|
15 | 15 | #include <errno.h> |
16 | 16 | #include <sstream> |
17 | 17 | #include <algorithm> |
| 18 | +#include <memory> |
18 | 19 |
|
19 | 20 | #ifdef _WIN32 |
20 | 21 | #include <io.h> |
21 | 22 | #include <direct.h> |
22 | 23 | #include <windows.h> |
23 | 24 | #include <winbase.h> /* needed for memory mapping of file functions */ |
| 25 | +#include <shlwapi.h> |
| 26 | + |
| 27 | +struct dir_handle_deleter { |
| 28 | + typedef HANDLE pointer; |
| 29 | + |
| 30 | + void operator()(HANDLE dirp) { |
| 31 | + if (dirp != INVALID_HANDLE_VALUE) { |
| 32 | + FindClose(dirp); |
| 33 | + } |
| 34 | + } |
| 35 | +}; |
| 36 | +typedef std::unique_ptr<HANDLE, dir_handle_deleter> unique_dir_handle_ptr; |
24 | 37 | #endif |
25 | 38 |
|
26 | 39 | #ifdef SCP_UNIX |
|
30 | 43 | #include <fnmatch.h> |
31 | 44 | #include <sys/stat.h> |
32 | 45 | #include <unistd.h> |
| 46 | +#include <libgen.h> |
| 47 | + |
| 48 | +struct dir_deleter { |
| 49 | + void operator()(DIR* dirp) { |
| 50 | + closedir(dirp); |
| 51 | + } |
| 52 | +}; |
| 53 | +typedef std::unique_ptr<DIR, dir_deleter> unique_dir_ptr; |
33 | 54 | #endif |
34 | 55 |
|
35 | 56 | #include "cfile/cfile.h" |
|
38 | 59 | #include "globalincs/pstypes.h" |
39 | 60 | #include "localization/localize.h" |
40 | 61 | #include "osapi/osapi.h" |
| 62 | +#include "parse/parselo.h" |
41 | 63 |
|
42 | 64 | #define CF_ROOTTYPE_PATH 0 |
43 | 65 | #define CF_ROOTTYPE_PACK 1 |
@@ -552,6 +574,62 @@ void cf_search_root_path(int root_index) |
552 | 574 | } |
553 | 575 |
|
554 | 576 | #if defined _WIN32 |
| 577 | + { |
| 578 | + // Check if the case matches the case as specified in Pathtypes |
| 579 | + // Since Windows paths are case insensitive this wouldn't cause issues here but other |
| 580 | + // platforms would fail to find data paths in this case so we show a nice error if we detect that here |
| 581 | + |
| 582 | + // Ignore the root since the case of that is allowed to differ (it's handled in the mod handling) |
| 583 | + if (i != CF_TYPE_ROOT) { |
| 584 | + // We use FindFirstFileNameW for this, it should hopefully work... |
| 585 | + // First, convert our path from ASCII/UTF-8 to wchar_t |
| 586 | + std::string search_string = search_path; |
| 587 | + // Remove any trailing directory separators |
| 588 | + if (search_string[search_string.size() - 1] == '\\') { |
| 589 | + search_string = search_string.substr(0, search_string.size() - 1); |
| 590 | + } |
| 591 | + |
| 592 | + char parent_name[MAX_PATH]; |
| 593 | + memset(parent_name, 0, sizeof(parent_name)); |
| 594 | + strcpy_s(parent_name, search_string.c_str()); |
| 595 | + |
| 596 | + CHAR file_name[MAX_PATH]; |
| 597 | + memset(file_name, 0, sizeof(file_name)); |
| 598 | + strcpy_s(file_name, search_string.c_str()); |
| 599 | + |
| 600 | + PathStripPathA(file_name); |
| 601 | + if (PathRemoveFileSpecA(parent_name)) { |
| 602 | + strcat_s(parent_name, "\\*"); |
| 603 | + |
| 604 | + WIN32_FIND_DATAA find_data; |
| 605 | + auto handle = unique_dir_handle_ptr(FindFirstFileA(parent_name, &find_data)); |
| 606 | + if (handle.get() != INVALID_HANDLE_VALUE) { |
| 607 | + do { |
| 608 | + if (stricmp(find_data.cFileName, file_name)) { |
| 609 | + // Not the same name, not even if we check case-insensitive |
| 610 | + continue; |
| 611 | + } |
| 612 | + |
| 613 | + // Same name, might have case differences |
| 614 | + if (!strcmp(find_data.cFileName, file_name)) { |
| 615 | + // Case matches, everything is alright. |
| 616 | + continue; |
| 617 | + } |
| 618 | + |
| 619 | + // We need to do some formatting on the parent_name in order to show a nice error message |
| 620 | + SCP_string parent_name_str = parent_name; |
| 621 | + parent_name_str = parent_name_str.substr(0, parent_name_str.size() - 1); // Remove trailing * |
| 622 | + parent_name_str += find_data.cFileName; |
| 623 | + |
| 624 | + // If we are still here then the case didn't match which means that we have to show the error message |
| 625 | + Error(LOCATION, "Data directory '%s' for path type '%s' does not match the required case! " |
| 626 | + "All data directories must exactly match the case specified by the engine or your mod " |
| 627 | + "will not work on other platforms.", parent_name_str.c_str(), Pathtypes[i].path); |
| 628 | + } while (FindNextFileA(handle.get(), &find_data) != 0); |
| 629 | + } |
| 630 | + } |
| 631 | + } |
| 632 | + } |
555 | 633 | strcat_s( search_path, "*.*" ); |
556 | 634 |
|
557 | 635 | intptr_t find_handle; |
@@ -588,21 +666,65 @@ void cf_search_root_path(int root_index) |
588 | 666 | _findclose( find_handle ); |
589 | 667 | } |
590 | 668 | #elif defined SCP_UNIX |
591 | | - DIR *dirp; |
592 | | - struct dirent *dir; |
| 669 | + auto dirp = unique_dir_ptr(opendir(search_path)); |
| 670 | + if (!dirp) |
| 671 | + { |
| 672 | + // If the directory does not exist then check if it might exist with a different case. If that's the case |
| 673 | + // we bail out with an error so inform the user that this is not valid. |
| 674 | + |
| 675 | + // On Unix we can have a different case for the search paths so we also need to account for that |
| 676 | + // We do that by looking at the parent of search_path and enumerating all directories and then check if any of |
| 677 | + // them are a case-insensitive match |
| 678 | + char dirname_copy[CF_MAX_PATHNAME_LENGTH]; |
| 679 | + memcpy(dirname_copy, search_path, sizeof(search_path)); |
| 680 | + // According to the documentation of directory_name and basename, the return value does not need to be freed |
| 681 | + auto directory_name = dirname(dirname_copy); |
| 682 | + |
| 683 | + char basename_copy[CF_MAX_PATHNAME_LENGTH]; |
| 684 | + memcpy(basename_copy, search_path, sizeof(search_path)); |
| 685 | + // According to the documentation of dirname and basename, the return value does not need to be freed |
| 686 | + auto search_name = basename(basename_copy); |
| 687 | + |
| 688 | + auto parentDirP = unique_dir_ptr(opendir(directory_name)); |
| 689 | + if (parentDirP) { |
| 690 | + struct dirent *dir = nullptr; |
| 691 | + while ((dir = readdir (parentDirP.get())) != nullptr) { |
| 692 | + |
| 693 | + if (stricmp(search_name, dir->d_name)) { |
| 694 | + continue; |
| 695 | + } |
| 696 | + |
| 697 | + SCP_string fn; |
| 698 | + sprintf(fn, "%s/%s", directory_name, dir->d_name); |
| 699 | + |
| 700 | + struct stat buf; |
| 701 | + if (stat(fn.c_str(), &buf) == -1) { |
| 702 | + continue; |
| 703 | + } |
593 | 704 |
|
594 | | - dirp = opendir (search_path); |
595 | | - if ( dirp ) { |
596 | | - while ((dir = readdir (dirp)) != NULL) |
| 705 | + if (S_ISDIR(buf.st_mode)) { |
| 706 | + // Found a case insensitive match |
| 707 | + // If the name is not exactly the same as the one we are currently searching then it's an error |
| 708 | + if (strcmp(search_name, dir->d_name)) { |
| 709 | + Error(LOCATION, "Data directory '%s' for path type '%s' does not match the required case! " |
| 710 | + "All data directories must exactly match the case specified by the engine or your mod " |
| 711 | + "will not work on other platforms.", fn.c_str(), Pathtypes[i].path); |
| 712 | + } |
| 713 | + break; |
| 714 | + } |
| 715 | + } |
| 716 | + } |
| 717 | + } else { |
| 718 | + struct dirent *dir = nullptr; |
| 719 | + while ((dir = readdir (dirp.get())) != NULL) |
597 | 720 | { |
598 | 721 | if (!fnmatch ("*.*", dir->d_name, 0)) |
599 | 722 | { |
600 | | - char fn[MAX_PATH]; |
601 | | - snprintf(fn, MAX_PATH-1, "%s%s", search_path, dir->d_name); |
602 | | - fn[MAX_PATH-1] = 0; |
| 723 | + SCP_string fn; |
| 724 | + sprintf(fn, "%s%s", search_path, dir->d_name); |
603 | 725 |
|
604 | 726 | struct stat buf; |
605 | | - if (stat(fn, &buf) == -1) { |
| 727 | + if (stat(fn.c_str(), &buf) == -1) { |
606 | 728 | continue; |
607 | 729 | } |
608 | 730 |
|
@@ -632,7 +754,6 @@ void cf_search_root_path(int root_index) |
632 | 754 | } |
633 | 755 | } |
634 | 756 | } |
635 | | - closedir(dirp); |
636 | 757 | } |
637 | 758 | #endif |
638 | 759 | } |
|
0 commit comments