Consolidate case-sensitive filesystem functions

Descent 3 is case-insensitive. It doesn’t matter if a file is named
“ppics.hog” or “PPPICS.HOG”. Descent 3 will load the file regardless. In
order to accomplish this, Descent 3 has to have special code for
case-sensitive filesystems. That code must take a fake case-insensitive
path and turn it into a real case-sensitive path.

Before this change, there was multiple chunks of code that helped turn
fake case-insensitive paths into real case-sensitive paths. There was
cf_FindRealFileNameCaseInsenstive(), mve_FindMovieFileRealName() and a
chunk of code in open_file_in_directory() that only exists if __LINUX__
is defined. This removes each of those pieces of code and replaces them
with a new cf_LocatePath() function.

Using the new cf_LocatePath() function has two main advantages over the
old way of doing things. First, having a single function is simpler than
having three different pieces of code. Second, the new cf_LocatePath()
function will make it easier to create a future commit. That future
commit will make Descent 3 look for files in more than just the -setdir
directory. Having a single function that’s responsible for determining
the true path of a file will make it much easier to create that future
commit.
This commit is contained in:
Jason Yundt 2024-05-22 07:23:17 -04:00
parent fc5f732347
commit 9d08314986
14 changed files with 124 additions and 211 deletions

View File

@ -565,7 +565,7 @@ bool InitGameModule(const char *name, module *mod) {
std::filesystem::path dll_name;
std::filesystem::path tmp_dll_name;
// Make the hog filename
lib_name = Base_directory / "netgames" / name;
lib_name = std::filesystem::path("netgames") / name;
lib_name.replace_extension(".d3m");
// Make the dll filename
dll_name = name;

View File

@ -921,7 +921,7 @@ bool LoadMission(const char *mssn) {
if (IS_MN3_FILE(mssn)) {
strcpy(mission, mssn);
ddio_MakePath(pathname, D3MissionsDir, mission, NULL);
ddio_MakePath(pathname, "missions", mission, NULL);
} else {
strcpy(mission, mssn);
strcpy(pathname, mssn);
@ -1645,9 +1645,7 @@ void DoMissionMovie(const char *movie) {
return;
#endif
if (movie && *movie) {
char mpath[_MAX_PATH];
ddio_MakePath(mpath, LocalD3Dir, "movies", movie, NULL);
PlayMovie(mpath);
PlayMovie(movie);
}
}
@ -1835,11 +1833,11 @@ bool mn3_Open(const char *mn3file) {
char voice_hog[_MAX_PATH*2];
if ((stricmp(filename, "d3") == 0) || (stricmp(filename, "training") == 0)) {
// Open audio hog file
ddio_MakePath(voice_hog, D3MissionsDir, "d3voice1.hog", nullptr); // Audio for levels 1-4
ddio_MakePath(voice_hog, "missions", "d3voice1.hog", nullptr); // Audio for levels 1-4
Mission_voice_hog_handle = cf_OpenLibrary(voice_hog);
} else if (stricmp(filename, "d3_2") == 0) {
// Open audio hog file
ddio_MakePath(voice_hog, D3MissionsDir, "d3voice2.hog", nullptr); // Audio for levels 5-17
ddio_MakePath(voice_hog, "missions", "d3voice2.hog", nullptr); // Audio for levels 5-17
Mission_voice_hog_handle = cf_OpenLibrary(voice_hog);
}
strcat(filename, ".gam");
@ -1855,7 +1853,7 @@ bool mn3_GetInfo(const char *mn3file, tMissionInfo *msn) {
char pathname[_MAX_PATH];
char filename[PSFILENAME_LEN + 1];
ddio_MakePath(pathname, D3MissionsDir, mn3file, nullptr);
ddio_MakePath(pathname, "missions", mn3file, nullptr);
handle = cf_OpenLibrary(pathname);
if (handle == 0) {
LOG_ERROR << "MISSION: MN3 failed to open.";

View File

@ -132,9 +132,7 @@ bool PPic_InitDatabase(void) {
// attempt to open the hog database
// --------------------------------
char fullpath[_MAX_PATH];
ddio_MakePath(fullpath, LocalD3Dir, PILOTPIC_DATABASE_HOG, NULL);
PilotPic_database_hog_handle = cf_OpenLibrary(fullpath);
PilotPic_database_hog_handle = cf_OpenLibrary(PILOTPIC_DATABASE_HOG);
if (PilotPic_database_hog_handle == 0) {
// there was an error opening the hog database

View File

@ -71,47 +71,11 @@ void mve_SetCallback(MovieFrameCallback_fp callBack) {
// used to tell movie library how to render movies.
void mve_SetRenderProperties(int16_t x, int16_t y, int16_t w, int16_t h, renderer_type type, bool hicolor) {}
#if defined(POSIX)
// locates the case-sensitive movie file name
std::filesystem::path mve_FindMovieFileRealName(const std::filesystem::path &movie) {
// split into directory and file...
std::filesystem::path t_file = movie.filename();
std::filesystem::path t_dir = movie.parent_path();
std::filesystem::path t_out;
// found a directory?
if (!t_dir.empty()) {
// map the bits (or fail)
t_out = cf_FindRealFileNameCaseInsensitive(t_file, t_dir);
if (t_out.empty())
return t_out;
// re-assemble
return (t_dir / t_out);
} else {
// just a file, map that
t_out = cf_FindRealFileNameCaseInsensitive(t_file);
if (t_out.empty())
return t_out;
// re-assemble
return t_out;
}
}
#endif
// plays a movie using the current screen.
int mve_PlayMovie(const std::filesystem::path &pMovieName, oeApplication *pApp) {
#ifndef NO_MOVIES
// first, find that movie..
std::filesystem::path real_name;
#if defined(POSIX)
real_name = mve_FindMovieFileRealName(pMovieName);
if (real_name.empty()) {
LOG_WARNING.printf("MOVIE: No such file %s", pMovieName.u8string().c_str());
return MVELIB_FILE_ERROR;
}
#else
real_name = pMovieName;
#endif
std::filesystem::path real_name = cf_LocatePath("movies" / pMovieName);
// open movie file.
FILE *hFile = fopen(real_name.u8string().c_str(), "rb");
if (hFile == nullptr) {
@ -354,17 +318,7 @@ void CallbackShowFrameNoFlip(unsigned char *buf, unsigned int bufw, unsigned int
intptr_t mve_SequenceStart(const char *mvename, void *fhandle, oeApplication *app, bool looping) {
#ifndef NO_MOVIES
// first, find that movie..
std::filesystem::path real_name;
#if defined(POSIX)
real_name = mve_FindMovieFileRealName(mvename);
if (real_name.empty()) {
LOG_WARNING.printf("MOVIE: No such file %s", mvename);
fhandle = nullptr;
return 0;
}
#else
real_name = mvename;
#endif
std::filesystem::path real_name = cf_LocatePath(std::filesystem::path("movies") / mvename);
fhandle = fopen(real_name.u8string().c_str(), "rb");
if (fhandle == nullptr) {

View File

@ -490,7 +490,7 @@ void Descent3() {
};
for (auto const &intro : intros) {
PlayMovie(Base_directory / "movies" / intro);
PlayMovie(intro);
}
}

View File

@ -1311,10 +1311,10 @@ void CheckHogfile() {
if (new_mn3) {
// close the mission hog file and open the new one
mn3_Close();
char hogpath[_MAX_PATH * 2];
ddio_MakePath(hogpath, D3MissionsDir, new_mn3, nullptr);
if (cfexist(hogpath)) {
mn3_Open(hogpath);
auto relative_path = std::filesystem::path("missions") / new_mn3;
auto absolute_path = cf_LocatePath(relative_path);
if (std::filesystem::exists(absolute_path)) {
mn3_Open(relative_path.u8string().c_str());
mem_free(Current_mission.filename);
Current_mission.filename = mem_strdup(new_mn3);
} else {

View File

@ -124,7 +124,7 @@ int gspy_Init() {
}
// Read the config, resolve the name if needed and setup the server addresses
cfgpath = Base_directory / gspy_cfgfilename;
cfgpath = cf_LocatePath(gspy_cfgfilename);
gspy_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

View File

@ -1465,42 +1465,36 @@ void InitIOSystems(bool editor) {
// Init hogfiles
INIT_MESSAGE(("Checking for HOG files."));
int d3_hid, extra_hid, sys_hid, extra13_hid;
char fullname[_MAX_PATH];
std::filesystem::path hog_name;
#ifdef DEMO
// DAJ d3_hid = cf_OpenLibrary("d3demo.hog");
ddio_MakePath(fullname, LocalD3Dir, "d3demo.hog", NULL);
hog_name = "d3demo.hog";
#else
ddio_MakePath(fullname, LocalD3Dir, "d3.hog", NULL);
hog_name = "d3.hog";
#endif
d3_hid = cf_OpenLibrary(fullname);
d3_hid = cf_OpenLibrary(hog_name);
// JC: Steam release uses extra1.hog instead of extra.hog, so try loading it first
// Open this file if it's present for stuff we might add later
ddio_MakePath(fullname, LocalD3Dir, "extra1.hog", NULL);
extra_hid = cf_OpenLibrary(fullname);
extra_hid = cf_OpenLibrary("extra1.hog");
if (extra_hid == 0) {
ddio_MakePath(fullname, LocalD3Dir, "extra.hog", NULL);
extra_hid = cf_OpenLibrary(fullname);
extra_hid = cf_OpenLibrary("extra.hog");
}
// JC: Steam release uses extra.hog instead of merc.hog, so try loading it last (so we don't conflict with the above)
// Open mercenary hog if it exists
ddio_MakePath(fullname, LocalD3Dir, "merc.hog", NULL);
merc_hid = cf_OpenLibrary(fullname);
merc_hid = cf_OpenLibrary("merc.hog");
if (merc_hid == 0) {
ddio_MakePath(fullname, LocalD3Dir, "extra.hog", NULL);
merc_hid = cf_OpenLibrary(fullname);
merc_hid = cf_OpenLibrary("extra.hog");
}
// Open this for extra 1.3 code (Black Pyro, etc)
ddio_MakePath(fullname, LocalD3Dir, "extra13.hog", NULL);
extra13_hid = cf_OpenLibrary(fullname);
extra13_hid = cf_OpenLibrary("extra13.hog");
// last library opened is the first to be searched for dynamic libs, so put
// this one at the end to find our newly build script libraries first
ddio_MakePath(fullname, LocalD3Dir, PRIMARY_HOG, NULL);
sys_hid = cf_OpenLibrary(fullname);
sys_hid = cf_OpenLibrary(PRIMARY_HOG);
// Check to see if there is a -mission command line option
// if there is, attempt to open that hog/mn3 so it can override such

View File

@ -330,9 +330,7 @@ void mmInterface::Create() {
m_movie = NULL;
static_menu_background = true;
} else {
char filename[_MAX_PATH];
ddio_MakePath(filename, Base_directory.u8string().c_str(), "movies", "mainmenu", NULL);
m_movie = StartMovie(filename, true);
m_movie = StartMovie("mainmenu", true);
if (!m_movie) //[ISB] Didn't find the menu movie?
{

View File

@ -608,7 +608,7 @@ int LoadMultiDLL(const char *name) {
});
// Make the hog filename
lib_name = Base_directory / "online" / name;
lib_name = std::filesystem::path("online") / name;
lib_name.replace_extension(".d3c");
// Make the dll filename
dll_name = name;

View File

@ -78,6 +78,68 @@ void cf_SetBaseDirectory(const std::filesystem::path &base_directory) {
Base_directory = base_directory;
}
std::filesystem::path cf_LocatePathCaseInsensitiveHelper(const std::filesystem::path &relative_path) {
#ifdef WIN32
std::filesystem::path result = Base_directory / relative_path;
if (std::filesystem::exists(result)) {
return result;
} else {
return {};
}
#else
auto &starting_dir = Base_directory;
// Dumb check, maybe there already all ok?
if (exists((starting_dir / relative_path))) {
return starting_dir / relative_path;
}
std::filesystem::path result, search_path, search_file;
search_path = starting_dir / relative_path.parent_path();
search_file = relative_path.filename();
// If directory does not exist, nothing to search.
if (!std::filesystem::is_directory(search_path) || search_file.empty()) {
return {};
}
// Search component in search_path
auto const &it = std::filesystem::directory_iterator(search_path);
auto found = std::find_if(it, end(it), [&search_file, &search_path, &result](const auto& dir_entry) {
return stricmp(dir_entry.path().filename().u8string().c_str(), search_file.u8string().c_str()) == 0;
});
if (found != end(it)) {
// Match, append to result
result = found->path();
search_path = result;
} else {
// Component not found, mission failed
return {};
}
return result;
#endif
}
/**
* Tries to find a relative path inside of Base_directory.
*
* @param relative_path A relative path that well hopefully find in
* Base_directory. You dont have to get the capitalization
* of relative_path correct, even on macOS and Linux.
*
* @return If the path is found, an absolute path thats inside
* Base_directory. Otherwise, a path that probably doesnt exist
* will be returned.
*/
std::filesystem::path cf_LocatePath(const std::filesystem::path &relative_path) {
ASSERT(("realative_path should be a relative path.", relative_path.is_relative()));
return cf_LocatePathCaseInsensitiveHelper(relative_path);
}
// Generates a cfile error
void ThrowCFileError(int type, CFILE *file, const char *msg) {
cfe.read_write = type;
@ -92,9 +154,9 @@ static void cf_Close();
static CFILE *open_file_in_lib(const char *filename);
// Opens a HOG file. Future calls to cfopen(), etc. will look in this HOG.
// Parameters: libname - the path & filename of the HOG file
// NOTE: libname must be valid for the entire execution of the program. Therefore, it should either
// be a fully-specified path name, or the current directory must not change.
// Parameters: libname - path to the HOG file, relative to Base_directory.
// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directory
// must not change.
// Returns: 0 if error, else library handle that can be used to close the library
int cf_OpenLibrary(const std::filesystem::path &libname) {
FILE *fp;
@ -106,25 +168,7 @@ int cf_OpenLibrary(const std::filesystem::path &libname) {
// allocation library structure
std::shared_ptr<library> lib = std::make_shared<library>();
// resolve library name
std::filesystem::path resolve_dir = libname.parent_path();
std::filesystem::path resolve_name = libname;
if (!resolve_dir.empty()) {
resolve_name = libname.filename();
}
std::filesystem::path t_out = cf_FindRealFileNameCaseInsensitive(resolve_name, resolve_dir);
if (t_out.empty()) {
return 0; // CF_NO_FILE
}
// re-assemble
if (!resolve_dir.empty())
lib->name = resolve_dir / t_out;
else
lib->name = t_out;
lib->name = cf_LocatePath(libname);
fp = fopen(lib->name.u8string().c_str(), "rb");
if (fp == nullptr) {
return 0; // CF_NO_FILE;
@ -363,42 +407,6 @@ CFILE *open_file_in_lib(const char *filename) {
return nullptr;
}
std::filesystem::path cf_FindRealFileNameCaseInsensitive(const std::filesystem::path &relative_path,
const std::filesystem::path &starting_dir) {
// Dumb check, maybe there already all ok?
if (exists((starting_dir / relative_path))) {
return relative_path.filename();
}
std::filesystem::path result, search_path, search_file;
search_path = starting_dir / relative_path.parent_path();
search_file = relative_path.filename();
// If directory does not exist, nothing to search.
if (!std::filesystem::is_directory(search_path) || search_file.empty()) {
return {};
}
// Search component in search_path
auto const &it = std::filesystem::directory_iterator(search_path);
auto found = std::find_if(it, end(it), [&search_file, &search_path, &result](const auto &dir_entry) {
return stricmp(dir_entry.path().filename().u8string().c_str(), search_file.u8string().c_str()) == 0;
});
if (found != end(it)) {
// Match, append to result
result = found->path();
search_path = result;
} else {
// Component not found, mission failed
return {};
}
return result.filename();
}
// look for the file in the specified directory
static CFILE *open_file_in_directory(const std::filesystem::path &filename, const char *mode,
const std::filesystem::path &directory);
@ -413,9 +421,12 @@ CFILE *open_file_in_directory(const std::filesystem::path &filename, const char
if (std::filesystem::is_directory(directory)) {
// Make a full path
using_filename = directory / filename;
} else {
// no directory specified, so just use filename passed
} else if (filename.is_absolute()) {
// no directory specified, and filename is an absolute path
using_filename = filename;
} else {
// no directory specified, and filename is a relative path
using_filename = cf_LocatePath(filename);
}
// set read or write mode
@ -426,34 +437,8 @@ CFILE *open_file_in_directory(const std::filesystem::path &filename, const char
fp = fopen(using_filename.u8string().c_str(), tmode);
if (!fp) {
#if defined(POSIX)
// If we tried to open file for reading, assume there maybe case-sensitive files
if (tmode[0] == 'r') {
// Try different cases of the filename
using_filename = cf_FindRealFileNameCaseInsensitive(filename, directory);
if (using_filename.empty()) {
// just give up
return nullptr;
}
if (std::filesystem::is_directory(directory)) {
// Make a full path
using_filename = directory / using_filename;
}
fp = fopen(using_filename.u8string().c_str(), tmode);
if (!fp) {
// no dice
return nullptr;
}
} else {
// Error on writing file
return nullptr;
}
#else
// We on incase-sensitive filesystem, no file means no file.
// File not found
return nullptr;
#endif
} else {
using_filename = filename;
}

View File

@ -148,13 +148,24 @@ extern std::filesystem::path Base_directory;
*/
void cf_SetBaseDirectory(const std::filesystem::path &base_directory);
/**
* Tries to find a relative path inside of Base_directory.
*
* @param relative_path A relative path that well hopefully find in
* Base_directory. You dont have to get the capitalization
* of relative_path correct, even on macOS and Linux.
*
* @return An absolute path thats inside Base_directory.
*/
std::filesystem::path cf_LocatePath(const std::filesystem::path &relative_path);
// See if a file is in a hog
bool cf_IsFileInHog(const std::filesystem::path &filename, const std::filesystem::path &hogname);
// Opens a HOG file. Future calls to cfopen(), etc. will look in this HOG.
// Parameters: libname - the path & filename of the HOG file
// NOTE: libname must be valid for the entire execution of the program. Therefore, it should either
// be a fully-specified path name, or the current directory must not change.
// Parameters: libname - path to the HOG file, relative to Base_directory.
// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directory
// must not change.
// Returns: 0 if error, else library handle that can be used to close the library
int cf_OpenLibrary(const std::filesystem::path &libname);
@ -162,17 +173,6 @@ int cf_OpenLibrary(const std::filesystem::path &libname);
// Parameters: handle: the handle returned by cf_OpenLibrary()
void cf_CloseLibrary(int handle);
/**
* Returns fixed case file name to actual case on disk for case-sensitive filesystems (Linux).
* @param relative_path the fixed case name to map to reality
* @param starting_dir optional directory to search within (default - current path)
* @return filename with actual case name or empty path if there no mapping in filesystem
* @note This function returns only filename without directory part, i.e.
* cf_FindRealFileNameCaseInsensitive("test/test.txt") will return only "test.txt" on success.
*/
std::filesystem::path cf_FindRealFileNameCaseInsensitive(const std::filesystem::path &relative_path,
const std::filesystem::path &starting_dir = ".");
/**
* Add directory path into paths to look in for files. If ext_list is empty,
* look in this directory for all files. Otherwise, the directory will only

View File

@ -76,55 +76,41 @@ TEST(D3, CFileLibrary) {
EXPECT_EQ(file_handle, nullptr);
}
TEST(D3, CFileCaseSensitiveSearchNew) {
TEST(D3, CFileLocatePath) {
const std::vector<std::filesystem::path> test_paths = {
std::filesystem::path("TestDir") / "CamelCase.txt",
std::filesystem::path("TestDir") / "lowercase.txt",
std::filesystem::path("TestDir") / "UPPERCASE.TXT",
};
std::filesystem::path filename_new = cf_FindRealFileNameCaseInsensitive("no-exist-file.txt", "no-exist-dir");
std::filesystem::path filename_new = cf_LocatePath(std::filesystem::path("no-exist-dir") / "no-exist-file.txt");
EXPECT_TRUE(filename_new.empty());
filename_new = cf_FindRealFileNameCaseInsensitive("no-exist-file.txt");
filename_new = cf_LocatePath("no-exist-file.txt");
EXPECT_TRUE(filename_new.empty());
auto cwd = std::filesystem::current_path();
for (auto const &item : test_paths) {
auto directory = cwd / item.parent_path();
cf_SetBaseDirectory(directory);
std::filesystem::path file = item.filename();
std::string file_lc = item.filename().u8string();
std::transform(file_lc.begin(), file_lc.end(), file_lc.begin(), ::tolower);
std::string file_uc = item.filename().u8string();
std::transform(file_uc.begin(), file_uc.end(), file_uc.begin(), ::toupper);
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_lc, directory).empty());
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_uc, directory).empty());
EXPECT_FALSE(cf_LocatePath(file_lc).empty());
EXPECT_FALSE(cf_LocatePath(file_uc).empty());
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(directory / file_lc).empty());
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(directory / file_uc).empty());
// Now try case-insensitive path with non-existing directory in search.
// Now try case-insensitive path with non-existing path.
// Expected not found on case-sensitive fs.
file_lc = item.u8string();
std::transform(file_lc.begin(), file_lc.end(), file_lc.begin(), ::tolower);
file_uc = item.u8string();
std::transform(file_uc.begin(), file_uc.end(), file_uc.begin(), ::toupper);
if (std::filesystem::is_regular_file(file_lc)) {
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_lc, cwd).empty());
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_lc).empty());
} else {
EXPECT_TRUE(cf_FindRealFileNameCaseInsensitive(file_lc, cwd).empty());
EXPECT_TRUE(cf_FindRealFileNameCaseInsensitive(file_lc).empty());
}
if (std::filesystem::is_regular_file(file_uc)) {
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_uc, cwd).empty());
EXPECT_FALSE(cf_FindRealFileNameCaseInsensitive(file_uc).empty());
} else {
EXPECT_TRUE(cf_FindRealFileNameCaseInsensitive(file_uc, cwd).empty());
EXPECT_TRUE(cf_FindRealFileNameCaseInsensitive(file_uc).empty());
}
EXPECT_TRUE(cf_LocatePath(file_lc).empty());
EXPECT_TRUE(cf_LocatePath(file_uc).empty());
}
}

View File

@ -183,8 +183,8 @@ bool mod_LoadModule(module *handle, const std::filesystem::path &imodfilename, i
handle->handle = dlopen(modfilename.u8string().c_str(), f);
if (!handle->handle) {
// ok we couldn't find the given name...try other ways
std::filesystem::path parent_path = modfilename.parent_path();
std::filesystem::path new_filename = cf_FindRealFileNameCaseInsensitive(modfilename.filename(), parent_path);
std::filesystem::path parent_path = modfilename.parent_path().filename();
std::filesystem::path new_filename = cf_LocatePath(parent_path / modfilename.filename());
if (new_filename.empty()) {
LOG_ERROR.printf("Module Load Err: %s", dlerror());