Skip to content

Commit 6ff7662

Browse files
committed
Search for path types regarless of case
Previously, if a mod used an upper case "Data" folder (which is the case for the Diaspora development files) the file enumeration would fail on case sensitive platforms (e.g. Linux) since FSO searched for a path with a lower case "data". These changes make it so that FSO looks for all instances of the path type name regeardless of case. Since the real path was also needed elsewhere I added a new cf_file field which stores the actual path which can then be used by fopen operations. This fixes #1182.
1 parent 2907c7a commit 6ff7662

File tree

3 files changed

+162
-54
lines changed

3 files changed

+162
-54
lines changed

code/cfile/cfilesystem.cpp

Lines changed: 120 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <fnmatch.h>
3131
#include <sys/stat.h>
3232
#include <unistd.h>
33+
#include <libgen.h>
3334
#endif
3435

3536
#include "cfile/cfile.h"
@@ -38,6 +39,7 @@
3839
#include "globalincs/pstypes.h"
3940
#include "localization/localize.h"
4041
#include "osapi/osapi.h"
42+
#include "parse/parselo.h"
4143

4244
#define CF_ROOTTYPE_PATH 0
4345
#define CF_ROOTTYPE_PACK 1
@@ -77,12 +79,13 @@ static int Num_path_roots = 0;
7779

7880
// Created by searching all roots in order. This means Files is then sorted by precedence.
7981
typedef struct cf_file {
80-
char name_ext[CF_MAX_FILENAME_LENGTH]; // Filename and extension
81-
int root_index; // Where in Roots this is located
82-
int pathtype_index; // Where in Paths this is located
83-
time_t write_time; // When it was last written
84-
int size; // How big it is in bytes
85-
int pack_offset; // For pack files, where it is at. 0 if not in a pack file. This can be used to tell if in a pack file.
82+
char name_ext[CF_MAX_FILENAME_LENGTH]; // Filename and extension
83+
int root_index; // Where in Roots this is located
84+
int pathtype_index; // Where in Paths this is located
85+
time_t write_time; // When it was last written
86+
int size; // How big it is in bytes
87+
int pack_offset; // For pack files, where it is at. 0 if not in a pack file. This can be used to tell if in a pack file.
88+
char* real_name; // For real files, the full path
8689
} cf_file;
8790

8891
#define CF_NUM_FILES_PER_BLOCK 512
@@ -115,6 +118,7 @@ cf_file *cf_create_file()
115118
if ( File_blocks[block] == NULL ) {
116119
File_blocks[block] = (cf_file_block *)vm_malloc( sizeof(cf_file_block) );
117120
Assert( File_blocks[block] != NULL);
121+
memset(File_blocks[block], 0, sizeof(cf_file_block));
118122
}
119123

120124
Num_files++;
@@ -534,6 +538,10 @@ void cf_search_root_path(int root_index)
534538
mprintf(( "Searching root '%s' ... ", root->path ));
535539

536540
char search_path[CF_MAX_PATHNAME_LENGTH];
541+
#ifdef SCP_UNIX
542+
// This map stores the mapping between a specific path type and the actual path that we use for it
543+
SCP_unordered_map<int, SCP_string> pathTypeToRealPath;
544+
#endif
537545

538546
for (i=CF_TYPE_ROOT; i<CF_MAX_PATH_TYPES; i++ ) {
539547

@@ -552,6 +560,7 @@ void cf_search_root_path(int root_index)
552560
}
553561

554562
#if defined _WIN32
563+
SCP_string search_directory = search_path;
555564
strcat_s( search_path, "*.*" );
556565

557566
intptr_t find_handle;
@@ -576,6 +585,11 @@ void cf_search_root_path(int root_index)
576585
file->size = find.size;
577586
file->pack_offset = 0; // Mark as a non-packed file
578587

588+
SCP_string file_name;
589+
sprintf(file_name, "%s%s%s", search_directory.c_str(), DIR_SEPARATOR_STR, find.name);
590+
591+
file->real_name = vm_strdup(file_name.c_str());
592+
579593
num_files++;
580594
//mprintf(( "Found file '%s'\n", file->name_ext ));
581595
}
@@ -588,21 +602,80 @@ void cf_search_root_path(int root_index)
588602
_findclose( find_handle );
589603
}
590604
#elif defined SCP_UNIX
591-
DIR *dirp;
592-
struct dirent *dir;
605+
DIR *dirp = nullptr;
606+
SCP_string search_dir;
607+
{
608+
if (i == CF_TYPE_ROOT) {
609+
// Don't search for the same name for the root case since we would be searching in other mod directories in that case
610+
dirp = opendir (search_path);
611+
search_dir.assign(search_path);
612+
} else {
613+
// On Unix we can have a different case for the search paths so we also need to account for that
614+
// We do that by looking at the parent of search_path and enumerating all directories and the check if any of
615+
// them are a case-insensitive match
616+
SCP_string directory_name;
617+
618+
auto parentPathIter = pathTypeToRealPath.find(Pathtypes[i].parent_index);
619+
620+
if (parentPathIter == pathTypeToRealPath.end()) {
621+
// No parent known yet, use the standard dirname
622+
char dirname_copy[CF_MAX_PATHNAME_LENGTH];
623+
memcpy(dirname_copy, search_path, sizeof(search_path));
624+
// According to the documentation of directory_name and basename, the return value does not need to be freed
625+
directory_name.assign(dirname(dirname_copy));
626+
} else {
627+
// we have a valid parent path -> use that
628+
directory_name = parentPathIter->second;
629+
}
630+
631+
char basename_copy[CF_MAX_PATHNAME_LENGTH];
632+
memcpy(basename_copy, search_path, sizeof(search_path));
633+
// According to the documentation of dirname and basename, the return value does not need to be freed
634+
auto search_name = basename(basename_copy);
635+
636+
auto parentDirP = opendir(directory_name.c_str());
637+
638+
if (parentDirP) {
639+
struct dirent *dir = nullptr;
640+
while ((dir = readdir (parentDirP)) != nullptr) {
641+
642+
if (stricmp(search_name, dir->d_name)) {
643+
continue;
644+
}
645+
646+
SCP_string fn;
647+
sprintf(fn, "%s/%s", directory_name.c_str(), dir->d_name);
648+
649+
struct stat buf;
650+
if (stat(fn.c_str(), &buf) == -1) {
651+
continue;
652+
}
653+
654+
if (S_ISDIR(buf.st_mode)) {
655+
// Found a case insensitive match
656+
dirp = opendir(fn.c_str());
657+
search_dir = fn;
658+
// We also need to store this in our mapping since we may need it in the future
659+
pathTypeToRealPath.insert(std::make_pair(i, fn));
660+
break;
661+
}
662+
}
663+
closedir(parentDirP);
664+
}
665+
}
666+
}
593667

594-
dirp = opendir (search_path);
595668
if ( dirp ) {
669+
struct dirent *dir = nullptr;
596670
while ((dir = readdir (dirp)) != NULL)
597671
{
598672
if (!fnmatch ("*.*", dir->d_name, 0))
599673
{
600-
char fn[MAX_PATH];
601-
snprintf(fn, MAX_PATH-1, "%s%s", search_path, dir->d_name);
602-
fn[MAX_PATH-1] = 0;
674+
SCP_string fn;
675+
sprintf(fn, "%s/%s", search_dir.c_str(), dir->d_name);
603676

604677
struct stat buf;
605-
if (stat(fn, &buf) == -1) {
678+
if (stat(fn.c_str(), &buf) == -1) {
606679
continue;
607680
}
608681

@@ -626,6 +699,8 @@ void cf_search_root_path(int root_index)
626699

627700
file->pack_offset = 0; // Mark as a non-packed file
628701

702+
file->real_name = vm_strdup(fn.c_str());
703+
629704
num_files++;
630705
//mprintf(( "Found file '%s'\n", file->name_ext ));
631706
}
@@ -827,6 +902,14 @@ void cf_free_secondary_filelist()
827902
// Init the file blocks
828903
for (i=0; i<CF_MAX_FILE_BLOCKS; i++ ) {
829904
if ( File_blocks[i] ) {
905+
// Free file paths
906+
for (auto& f : File_blocks[i]->files) {
907+
if (f.real_name) {
908+
vm_free(f.real_name);
909+
f.real_name = nullptr;
910+
}
911+
}
912+
830913
vm_free( File_blocks[i] );
831914
File_blocks[i] = NULL;
832915
}
@@ -985,17 +1068,14 @@ int cf_find_file_location( const char *filespec, int pathtype, int max_out, char
9851068
*offset = (size_t)f->pack_offset;
9861069

9871070
if (pack_filename) {
988-
cf_root *r = cf_get_root(f->root_index);
989-
990-
strncpy( pack_filename, r->path, max_out );
991-
9921071
if (f->pack_offset < 1) {
993-
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
994-
995-
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
996-
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1072+
// This is a real file, return the actual file path
1073+
strncpy( pack_filename, f->real_name, max_out );
1074+
} else {
1075+
// File is in a pack file
1076+
cf_root *r = cf_get_root(f->root_index);
9971077

998-
strcat_s( pack_filename, max_out, f->name_ext );
1078+
strncpy( pack_filename, r->path, max_out );
9991079
}
10001080
}
10011081

@@ -1013,19 +1093,14 @@ int cf_find_file_location( const char *filespec, int pathtype, int max_out, char
10131093
*offset = (size_t)f->pack_offset;
10141094

10151095
if (pack_filename) {
1016-
cf_root *r = cf_get_root(f->root_index);
1017-
1018-
strcpy( pack_filename, r->path );
1019-
10201096
if (f->pack_offset < 1) {
1021-
if ( strlen(Pathtypes[f->pathtype_index].path) ) {
1022-
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
1023-
1024-
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1025-
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1026-
}
1097+
// This is a real file, return the actual file path
1098+
strncpy( pack_filename, f->real_name, max_out );
1099+
} else {
1100+
// File is in a pack file
1101+
cf_root *r = cf_get_root(f->root_index);
10271102

1028-
strcat_s( pack_filename, max_out, f->name_ext );
1103+
strncpy( pack_filename, r->path, max_out );
10291104
}
10301105
}
10311106

@@ -1260,17 +1335,14 @@ int cf_find_file_location_ext( const char *filename, const int ext_num, const ch
12601335
*offset = f->pack_offset;
12611336

12621337
if (pack_filename) {
1263-
cf_root *r = cf_get_root(f->root_index);
1264-
1265-
strncpy( pack_filename, r->path, max_out );
1266-
12671338
if (f->pack_offset < 1) {
1268-
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
1339+
// This is a real file, return the actual file path
1340+
strncpy( pack_filename, f->real_name, max_out );
1341+
} else {
1342+
// File is in a pack file
1343+
cf_root *r = cf_get_root(f->root_index);
12691344

1270-
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1271-
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1272-
1273-
strcat_s( pack_filename, max_out, f->name_ext );
1345+
strncpy( pack_filename, r->path, max_out );
12741346
}
12751347
}
12761348

@@ -1291,20 +1363,14 @@ int cf_find_file_location_ext( const char *filename, const int ext_num, const ch
12911363
*offset = f->pack_offset;
12921364

12931365
if (pack_filename) {
1294-
cf_root *r = cf_get_root(f->root_index);
1295-
1296-
strcpy( pack_filename, r->path );
1297-
12981366
if (f->pack_offset < 1) {
1367+
// This is a real file, return the actual file path
1368+
strncpy( pack_filename, f->real_name, max_out );
1369+
} else {
1370+
// File is in a pack file
1371+
cf_root *r = cf_get_root(f->root_index);
12991372

1300-
if ( strlen(Pathtypes[f->pathtype_index].path) ) {
1301-
strcat_s( pack_filename, max_out, Pathtypes[f->pathtype_index].path );
1302-
1303-
if ( pack_filename[strlen(pack_filename)-1] != DIR_SEPARATOR_CHAR )
1304-
strcat_s( pack_filename, max_out, DIR_SEPARATOR_STR );
1305-
}
1306-
1307-
strcat_s( pack_filename, max_out, f->name_ext );
1373+
strncpy( pack_filename, r->path, max_out );
13081374
}
13091375
}
13101376

test/src/cfile/cfile.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
#include <gtest/gtest.h>
3+
#include <graphics/font.h>
4+
5+
#include "util/FSTestFixture.h"
6+
7+
class CFileTest : public test::FSTestFixture {
8+
public:
9+
CFileTest() : test::FSTestFixture(INIT_NONE) {
10+
pushModDir("cfile");
11+
}
12+
13+
protected:
14+
virtual void SetUp() override {
15+
test::FSTestFixture::SetUp();
16+
}
17+
virtual void TearDown() override {
18+
test::FSTestFixture::TearDown();
19+
20+
cfile_close();
21+
}
22+
};
23+
24+
TEST_F(CFileTest, wrong_data_case) {
25+
SCP_string cfile_dir(TEST_DATA_PATH);
26+
cfile_dir += DIR_SEPARATOR_CHAR;
27+
cfile_dir += "test"; // Cfile expects something after the path
28+
29+
ASSERT_FALSE(cfile_init(cfile_dir.c_str()));
30+
31+
ASSERT_TRUE(cf_exists("ships.tbl", CF_TYPE_TABLES));
32+
}
33+
34+
TEST_F(CFileTest, right_data_case) {
35+
SCP_string cfile_dir(TEST_DATA_PATH);
36+
cfile_dir += DIR_SEPARATOR_CHAR;
37+
cfile_dir += "test"; // Cfile expects something after the path
38+
39+
ASSERT_FALSE(cfile_init(cfile_dir.c_str()));
40+
41+
ASSERT_TRUE(cf_exists("ships.tbl", CF_TYPE_TABLES));
42+
}

test/test_data/cfile/right_data_case/data/tables/ships.tbl

Whitespace-only changes.

0 commit comments

Comments
 (0)