diff --git a/Descent3/Game2DLL.cpp b/Descent3/Game2DLL.cpp index 1dd1718ae..cf080a73e 100644 --- a/Descent3/Game2DLL.cpp +++ b/Descent3/Game2DLL.cpp @@ -573,8 +573,7 @@ bool InitGameModule(const char *name, module *mod) { // Open the hog file if (!cf_OpenLibrary(lib_name)) { - tmp_dll_name = Base_directory / "netgames" / name; - tmp_dll_name.replace_extension(".d3m"); + tmp_dll_name = cf_LocatePath(lib_name).u8string().c_str(); Multi_game_dll_name.clear(); goto loaddll; } diff --git a/Descent3/ambient.cpp b/Descent3/ambient.cpp index 7819066d3..518ba27dd 100644 --- a/Descent3/ambient.cpp +++ b/Descent3/ambient.cpp @@ -293,7 +293,7 @@ void WriteAmbientData() { CFILE *ofile; #ifndef NEWEDITOR - ddio_MakePath(filename, Base_directory.u8string().c_str(), "data", "misc", AMBIENT_FILE_NAME, NULL); + ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "data", "misc", AMBIENT_FILE_NAME, NULL); #else ddio_MakePath(filename, D3HogDir, "data", "misc", AMBIENT_FILE_NAME, NULL); #endif diff --git a/Descent3/demofile.cpp b/Descent3/demofile.cpp index d5e33d05e..7ba81f4bb 100644 --- a/Descent3/demofile.cpp +++ b/Descent3/demofile.cpp @@ -359,7 +359,7 @@ void DemoToggleRecording() { if (stricmp(szfile + (strlen(szfile) - 4), ".dem") != 0) { strcat(szfile, ".dem"); } - Demo_fname = Base_directory / "demo" / szfile; + Demo_fname = cf_GetWritableBaseDirectory() / "demo" / szfile; LOG_INFO.printf("Saving demo to file: %s", Demo_fname.u8string().c_str()); // Try to create the file Demo_cfp = cfopen(Demo_fname, "wb"); @@ -1408,7 +1408,7 @@ bool LoadDemoDialog() { // return false; // #else - std::filesystem::path file = Base_directory / "demo"; + std::filesystem::path file = cf_GetWritableBaseDirectory() / "demo"; if (DoPathFileDialog(false, file, TXT_VIEWDEMO, {"*.dem"}, PFDF_FILEMUSTEXIST)) { Demo_fname = file; diff --git a/Descent3/game.cpp b/Descent3/game.cpp index be6ecf6a3..9a74c0a3d 100644 --- a/Descent3/game.cpp +++ b/Descent3/game.cpp @@ -1279,7 +1279,7 @@ void DoScreenshot() { count = 1; while (!done) { snprintf(str, sizeof(str), "Screenshot%.3d.png", count); - ddio_MakePath(filename, Base_directory.u8string().c_str(), str, NULL); + ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), str, NULL); infile = (CFILE *)cfopen(filename, "rb"); if (infile == NULL) { done = 1; diff --git a/Descent3/gamesave.cpp b/Descent3/gamesave.cpp index 314fb5d34..bd44e1ed6 100644 --- a/Descent3/gamesave.cpp +++ b/Descent3/gamesave.cpp @@ -348,7 +348,7 @@ void QuickSaveGame() { i = Quicksave_game_slot; snprintf(filename, sizeof(filename), "saveg00%d", i); - ddio_MakePath(pathname, Base_directory.u8string().c_str(), "savegame", filename, NULL); + ddio_MakePath(pathname, cf_GetWritableBaseDirectory().u8string().c_str(), "savegame", filename, NULL); fp = fopen(pathname, "rb"); if (fp) { @@ -391,7 +391,7 @@ void SaveGameDialog() { #endif // setup paths. - ddio_MakePath(savegame_dir, Base_directory.u8string().c_str(), "savegame", NULL); + ddio_MakePath(savegame_dir, cf_GetWritableBaseDirectory().u8string().c_str(), "savegame", NULL); // ddio_MakePath(pathname, savegame_dir, "*.sav", NULL); -unused // create savegame directory if it didn't exist before. @@ -543,7 +543,7 @@ void __cdecl LoadGameDialogCB(newuiTiledWindow *wnd, void *data) LOG_DEBUG.printf("savegame slot=%d", id - SAVE_HOTSPOT_ID); - ddio_MakePath(savegame_dir, Base_directory.u8string().c_str(), "savegame", NULL); + ddio_MakePath(savegame_dir, cf_GetWritableBaseDirectory().u8string().c_str(), "savegame", NULL); snprintf(filename, sizeof(filename), "saveg00%d", (id - SAVE_HOTSPOT_ID)); ddio_MakePath(pathname, savegame_dir, filename, NULL); @@ -586,7 +586,7 @@ bool LoadGameDialog() { } // setup paths. - ddio_MakePath(savegame_dir, Base_directory.u8string().c_str(), "savegame", NULL); + ddio_MakePath(savegame_dir, cf_GetWritableBaseDirectory().u8string().c_str(), "savegame", NULL); ddio_MakePath(pathname, savegame_dir, "*.sav", NULL); // create savegame directory if it didn't exist before. diff --git a/Descent3/init.cpp b/Descent3/init.cpp index ca31afcf4..fcec5fce7 100644 --- a/Descent3/init.cpp +++ b/Descent3/init.cpp @@ -1390,12 +1390,12 @@ void LoadGameSettings() { void InitIOSystems(bool editor) { ddio_init_info io_info; - // Set the base directory + // Set the writable base directory int dirarg = FindArg("-setdir"); int exedirarg = FindArg("-useexedir"); - std::filesystem::path initial_base_directory; + std::filesystem::path writable_base_directory; if (dirarg) { - initial_base_directory = GameArgs[dirarg + 1]; + writable_base_directory = GameArgs[dirarg + 1]; } else if (exedirarg) { char exec_path[_MAX_PATH]; memset(exec_path, 0, sizeof(exec_path)); @@ -1404,15 +1404,28 @@ void InitIOSystems(bool editor) { Error("Failed to get executable path\n"); } else { std::filesystem::path executablePath(exec_path); - initial_base_directory = executablePath.parent_path(); - LOG_INFO << "Using working directory of " << Base_directory; + writable_base_directory = executablePath.parent_path(); + LOG_INFO << "Using working directory of " << writable_base_directory; } } else { - initial_base_directory = std::filesystem::current_path(); + writable_base_directory = std::filesystem::current_path(); } - cf_SetBaseDirectory(initial_base_directory); - ddio_SetWorkingDir(Base_directory.u8string().c_str()); + ddio_SetWorkingDir(writable_base_directory.u8string().c_str()); + cf_AddBaseDirectory(writable_base_directory); + + // Set any additional base directories + auto additionaldirarg = 0; + while (0 != (additionaldirarg = FindArg("-additionaldir", additionaldirarg))) { + const auto dir_to_add = GetArg(additionaldirarg + 1); + if (dir_to_add == NULL) { + LOG_WARNING << "-additionaldir was at the end of the argument list. It should never be at the end of the argument list."; + break; + } else { + cf_AddBaseDirectory(std::filesystem::path(dir_to_add)); + additionaldirarg += 2; + } + } Descent->set_defer_handler(D3DeferHandler); @@ -2015,7 +2028,7 @@ void SetupTempDirectory(void) { exit(1); } // restore working dir - ddio_SetWorkingDir(Base_directory.u8string().c_str()); + ddio_SetWorkingDir(cf_GetWritableBaseDirectory().u8string().c_str()); } void DeleteTempFiles() { diff --git a/Descent3/menu.cpp b/Descent3/menu.cpp index 7ddd21713..417cd3800 100644 --- a/Descent3/menu.cpp +++ b/Descent3/menu.cpp @@ -1061,51 +1061,55 @@ bool ProcessCommandLine() { #define TRAINING_MISSION_NAME "Pilot Training" /** - * Count singleplayer missions in directory. Mission should have .mn3 extension. - * @param missions_directory where to search missions. Should be a valid directory. + * Count singleplayer missions in directories. Mission should have .mn3 extension. + * @param missions_directories where to search missions. Should be a list of valid directories. * @return count of found missions */ -static inline int count_missions(const std::filesystem::path &missions_directory) { +static inline int count_missions(const std::vector &missions_directories) { int c = 0; - ddio_DoForeachFile(missions_directory, std::regex(".*\\.mn3"), [&c](const std::filesystem::path &path) { - if (stricmp(path.filename().u8string().c_str(), "d3_2.mn3") == 0) - return; - LOG_DEBUG.printf("Mission path: %s", path.u8string().c_str()); - tMissionInfo msninfo{}; - GetMissionInfo(path.filename().u8string().c_str(), &msninfo); + for (const auto &missions_directory : missions_directories) { + ddio_DoForeachFile(missions_directory, std::regex(".*\\.mn3"), [&c](const std::filesystem::path &path) { + if (stricmp(path.filename().u8string().c_str(), "d3_2.mn3") == 0) + return; + LOG_DEBUG.printf("Mission path: %s", path.u8string().c_str()); + tMissionInfo msninfo{}; + GetMissionInfo(path.filename().u8string().c_str(), &msninfo); - if (msninfo.name[0] && msninfo.single) { - LOG_DEBUG.printf("Name: %s", msninfo.name); - c++; - if (!(c % 2)) - DoWaitMessage(true); - } else { - LOG_DEBUG.printf("Illegal or multiplayer mission: %s", path.u8string().c_str()); - } - }); + if (msninfo.name[0] && msninfo.single) { + LOG_DEBUG.printf("Name: %s", msninfo.name); + c++; + if (!(c % 2)) + DoWaitMessage(true); + } else { + LOG_DEBUG.printf("Illegal or multiplayer mission: %s", path.u8string().c_str()); + } + }); + } return c; } static inline int generate_mission_listbox(newuiListBox *lb, int n_maxfiles, char **filelist, - const std::filesystem::path &missions_directory) { + const std::vector &missions_directories) { int c = 0; - ddio_DoForeachFile( - missions_directory, std::regex(".*\\.mn3"), [&c, &lb, &n_maxfiles, &filelist](const std::filesystem::path &path) { - tMissionInfo msninfo{}; - if (c < n_maxfiles) { - if (stricmp(path.filename().u8string().c_str(), "d3_2.mn3") == 0) - return; - if (GetMissionInfo(path.filename().u8string().c_str(), &msninfo) && msninfo.name[0] && msninfo.single) { - filelist[c] = mem_strdup(path.filename().u8string().c_str()); - lb->AddItem(msninfo.name); - c++; - if (!(c % 2)) - DoWaitMessage(true); + for (const auto &missions_directory : missions_directories) { + ddio_DoForeachFile( + missions_directory, std::regex(".*\\.mn3"), [&c, &lb, &n_maxfiles, &filelist](const std::filesystem::path &path) { + tMissionInfo msninfo{}; + if (c < n_maxfiles) { + if (stricmp(path.filename().u8string().c_str(), "d3_2.mn3") == 0) + return; + if (GetMissionInfo(path.filename().u8string().c_str(), &msninfo) && msninfo.name[0] && msninfo.single) { + filelist[c] = mem_strdup(path.filename().u8string().c_str()); + lb->AddItem(msninfo.name); + c++; + if (!(c % 2)) + DoWaitMessage(true); + } } - } - }); + }); + } return c; } @@ -1182,8 +1186,9 @@ bool MenuNewGame() { // add mission names to listbox // count valid mission files. // add a please wait dialog here. + auto missions_directories = cf_LocateMultiplePaths("missions"); n_missions = 0; - n_missions += count_missions(D3MissionsDir); + n_missions += count_missions(missions_directories); if (n_missions) { // allocate extra mission slot because of check below which adds a name to the filelist. filelist = (char **)mem_malloc(sizeof(char *) * (n_missions + 1)); @@ -1196,7 +1201,7 @@ bool MenuNewGame() { goto missions_fail; } // generate real listbox now. - generate_mission_listbox(msn_lb, n_missions, filelist, D3MissionsDir); + generate_mission_listbox(msn_lb, n_missions, filelist, missions_directories); // #ifdef RELEASE int k; for (k = 0; k < n_missions; k++) { diff --git a/Descent3/multi.cpp b/Descent3/multi.cpp index f1825e6b2..543d298ed 100644 --- a/Descent3/multi.cpp +++ b/Descent3/multi.cpp @@ -8010,23 +8010,23 @@ char *GetFileNameFromPlayerAndID(int16_t playernum, int16_t id) { break; case NETFILE_ID_SHIP_TEX: if (NetPlayers[playernum].ship_logo[0]) - ddio_MakePath(rval, Base_directory.u8string().c_str(), "custom", "graphics", NetPlayers[playernum].ship_logo, NULL); + ddio_MakePath(rval, cf_GetWritableBaseDirectory().u8string().c_str(), "custom", "graphics", NetPlayers[playernum].ship_logo, NULL); break; case NETFILE_ID_VOICE_TAUNT1: if (NetPlayers[playernum].voice_taunt1[0]) - ddio_MakePath(rval, Base_directory.u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt1, NULL); + ddio_MakePath(rval, cf_GetWritableBaseDirectory().u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt1, NULL); break; case NETFILE_ID_VOICE_TAUNT2: if (NetPlayers[playernum].voice_taunt2[0]) - ddio_MakePath(rval, Base_directory.u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt2, NULL); + ddio_MakePath(rval, cf_GetWritableBaseDirectory().u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt2, NULL); break; case NETFILE_ID_VOICE_TAUNT3: if (NetPlayers[playernum].voice_taunt3[0]) - ddio_MakePath(rval, Base_directory.u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt3, NULL); + ddio_MakePath(rval, cf_GetWritableBaseDirectory().u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt3, NULL); break; case NETFILE_ID_VOICE_TAUNT4: if (NetPlayers[playernum].voice_taunt4[0]) - ddio_MakePath(rval, Base_directory.u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt4, NULL); + ddio_MakePath(rval, cf_GetWritableBaseDirectory().u8string().c_str(), "custom", "sounds", NetPlayers[playernum].voice_taunt4, NULL); break; default: LOG_FATAL.printf("Unknown id (%d) passed to GetFileNameFromPlayerAndID()", id); diff --git a/Descent3/multi_dll_mgr.cpp b/Descent3/multi_dll_mgr.cpp index fe678480b..d7e3d961d 100644 --- a/Descent3/multi_dll_mgr.cpp +++ b/Descent3/multi_dll_mgr.cpp @@ -520,6 +520,9 @@ void GetMultiAPI(multi_api *api) { api->fp[109] = (int *)GetRankIndex; api->fp[110] = (int *)CheckGetD3M; api->fp[111] = (int *)ddio_DoForeachFile; + api->fp[112] = (int *)cf_LocatePath; + api->fp[113] = (int *)cf_LocateMultiplePaths; + api->fp[114] = (int *)cf_GetWritableBaseDirectory; // Variable pointers api->vp[0] = (int *)&Player_num; @@ -527,7 +530,7 @@ void GetMultiAPI(multi_api *api) { api->vp[2] = (int *)&Game_is_master_tracker_game; api->vp[3] = (int *)&Game_mode; api->vp[4] = (int *)NULL; // Current_pilot; no longer a struct - api->vp[5] = (int *)&Base_directory; + api->vp[5] = (int *)NULL; // Base_directory; no longer exists api->vp[6] = (int *)&MultiDLLGameStarting; api->vp[7] = (int *)MTPilotinfo; api->vp[8] = (int *)&Num_network_games_known; @@ -598,7 +601,7 @@ int LoadMultiDLL(const char *name) { if (MultiDLLHandle.handle) FreeMultiDLL(); - std::filesystem::path dll_path_name = Base_directory / "online"; + std::filesystem::path dll_path_name = cf_GetWritableBaseDirectory() / "online"; ddio_DoForeachFile(dll_path_name, std::regex(".+\\.tmp"), [](const std::filesystem::path& path, ...) { std::error_code ec; std::filesystem::remove(path, ec); @@ -616,7 +619,7 @@ int LoadMultiDLL(const char *name) { // Open the hog file if (!cf_OpenLibrary(lib_name)) { - tmp_dll_name = Base_directory / "online" / name; + tmp_dll_name = std::filesystem::path("online") / name; tmp_dll_name.replace_extension(".d3c"); Multi_conn_dll_name.clear(); goto loaddll; diff --git a/Descent3/multi_ui.cpp b/Descent3/multi_ui.cpp index 5a6bf2134..7ff191151 100644 --- a/Descent3/multi_ui.cpp +++ b/Descent3/multi_ui.cpp @@ -442,11 +442,12 @@ int MainMultiplayerMenu() { std::vector dllnames; - ddio_DoForeachFile(Base_directory / "online", std::regex(".*\\.d3c"), - [&dllnames](const std::filesystem::path &path) { - std::string filename = path.stem().string(); + for (const auto &online_directory : cf_LocateMultiplePaths("online")) { + ddio_DoForeachFile(online_directory, std::regex(".*\\.d3c"), + [&dllnames](const std::filesystem::path &path) { + std::string filename = path.stem().string(); - std::replace(filename.begin(), filename.end(), '~', '/'); + std::replace(filename.begin(), filename.end(), '~', '/'); // Place PXO_NAME first in list if (stricmp(filename.c_str(), PXO_NAME) == 0) { @@ -455,6 +456,7 @@ int MainMultiplayerMenu() { dllnames.push_back(filename); } }); + } for (auto const &name : dllnames) { lists->AddItem(name.c_str()); @@ -989,7 +991,7 @@ void DoMultiAllowed(void) { } void MultiDoConfigSave() { - std::filesystem::path file = Base_directory / "custom" / "settings"; + std::filesystem::path file = cf_GetWritableBaseDirectory() / "custom" / "settings"; if (DoPathFileDialog(true, file, TXT_MULTISAVESET, {"*.mps"}, 0)) { file.replace_extension(".mps"); MultiSaveSettings(file); @@ -997,7 +999,7 @@ void MultiDoConfigSave() { } void MultiDoConfigLoad() { - std::filesystem::path file = Base_directory / "custom" / "settings"; + std::filesystem::path file = cf_GetWritableBaseDirectory() / "custom" / "settings"; if (DoPathFileDialog(false, file, TXT_MULTILOADSET, {"*.mps"}, PFDF_FILEMUSTEXIST)) MultiLoadSettings(file); } diff --git a/Descent3/pilot.cpp b/Descent3/pilot.cpp index d8db808e4..244aa5871 100644 --- a/Descent3/pilot.cpp +++ b/Descent3/pilot.cpp @@ -1581,7 +1581,7 @@ bool PltDelete(pilot *Pilot) { std::string pfilename = Pilot->get_filename(); std::error_code ec; if (!pfilename.empty()) { - return std::filesystem::remove(Base_directory / pfilename, ec); + return std::filesystem::remove(cf_GetWritableBaseDirectory() / pfilename, ec); } else { Int3(); // this is odd @@ -1595,7 +1595,7 @@ bool PltDelete(pilot *Pilot) { PltMakeFNValid(pname); pfilename = std::string(pname) + PLTEXTENSION; - return std::filesystem::remove(Base_directory / pfilename, ec); + return std::filesystem::remove(cf_GetWritableBaseDirectory() / pfilename, ec); } } @@ -1634,7 +1634,7 @@ void PltReadFile(pilot *Pilot, bool keyconfig, bool missiondata) { return; // open and process file - std::filesystem::path filename = Base_directory / pfilename; + std::filesystem::path filename = cf_GetWritableBaseDirectory() / pfilename; try { file = cfopen(filename, "rb"); if (!file) @@ -1698,7 +1698,7 @@ std::vector PltGetPilots(std::string ignore_filename, int display_d break; } - ddio_DoForeachFile(Base_directory, wildcard, [&ignore_filename, &result](const std::filesystem::path &path) { + ddio_DoForeachFile(cf_GetWritableBaseDirectory(), wildcard, [&ignore_filename, &result](const std::filesystem::path &path) { std::string pilot = path.filename().u8string(); if (!ignore_filename.empty() && stricmp(ignore_filename.c_str(), pilot.c_str()) == 0) { LOG_INFO.printf("Getting Pilots... found %s, but ignoring", pilot.c_str()); @@ -3274,7 +3274,7 @@ void _ReadOldPilotFile(pilot *Pilot, bool keyconfig, bool missiondata) { std::string pfilename = Pilot->get_filename(); // open and process file - std::filesystem::path filename = Base_directory / pfilename; + std::filesystem::path filename = cf_GetWritableBaseDirectory() / pfilename; CFILE *file = cfopen(filename, "rb"); if (!file) return; diff --git a/Descent3/pilot_class.cpp b/Descent3/pilot_class.cpp index c1d91d246..d459fff1a 100644 --- a/Descent3/pilot_class.cpp +++ b/Descent3/pilot_class.cpp @@ -415,7 +415,7 @@ int pilot::flush(bool new_file) { } CFILE *file; - std::filesystem::path real_filename = Base_directory / filename; + std::filesystem::path real_filename = cf_GetWritableBaseDirectory() / filename; if (new_file && cfexist(real_filename)) { // the file already exists, we can't write out @@ -497,7 +497,7 @@ int pilot::read(bool skip_config, bool skip_mission_data) { } CFILE *file; - std::filesystem::path real_filename = Base_directory / filename; + std::filesystem::path real_filename = cf_GetWritableBaseDirectory() / filename; if (!cfexist(real_filename)) { // the file already exists, we can't write out diff --git a/cfile/cfile.cpp b/cfile/cfile.cpp index ab1a6e095..27475f2fd 100644 --- a/cfile/cfile.cpp +++ b/cfile/cfile.cpp @@ -54,8 +54,15 @@ struct library { FILE *file = nullptr; // pointer to file for this lib, if no one using it }; -// The "root" directory of the D3 file tree -std::filesystem::path Base_directory; +/* The "root" directories of the D3 file tree + * + * Directories that come later in the list override directories that come + * earlier in the list. For example, if Base_directories[0] / "d3.hog" exists + * and Base_directories[1] / "d3.hog" also exists, then the one in + * Base_directories[1] will get used. The one in Base_directories[0] will be + * ignored. + */ +std::vector Base_directories = {}; // Map of paths. If value of entry is true, path is only for specific extensions std::map paths; @@ -74,20 +81,32 @@ const char *eof_error = "Unexpected end of file"; /* This function should be called at least once before you use anything else * from this module. */ -void cf_SetBaseDirectory(const std::filesystem::path &base_directory) { - Base_directory = base_directory; +void cf_AddBaseDirectory(const std::filesystem::path &base_directory) { + if (std::filesystem::exists(base_directory)) { + Base_directories.push_back(base_directory); + } else { + LOG_WARNING << "Ignoring nonexistent base directory: " << base_directory; + } +} + +/* After you call this function, you must call cf_AddBaseDirectory() at least + * once before you use anything else from this module. + */ +void cf_ClearBaseDirectories() { + Base_directories.clear(); } -std::filesystem::path cf_LocatePathCaseInsensitiveHelper(const std::filesystem::path &relative_path) { + +std::filesystem::path cf_LocatePathCaseInsensitiveHelper(const std::filesystem::path &relative_path, + const std::filesystem::path &starting_dir) { #ifdef WIN32 - std::filesystem::path result = Base_directory / relative_path; + std::filesystem::path result = starting_dir / 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; @@ -124,20 +143,66 @@ std::filesystem::path cf_LocatePathCaseInsensitiveHelper(const std::filesystem:: #endif } +std::vector cf_LocatePathMultiplePathsHelper(const std::filesystem::path &relative_path, + bool stop_after_first_result) { + ASSERT(("realative_path should be a relative path.", relative_path.is_relative())); + std::vector return_value = { }; + for (auto base_directories_iterator = Base_directories.rbegin(); + base_directories_iterator != Base_directories.rend(); + ++base_directories_iterator) { + ASSERT(("base_directory should be an absolute path.", base_directories_iterator->is_absolute())); + auto to_append = cf_LocatePathCaseInsensitiveHelper(relative_path, *base_directories_iterator); + ASSERT(("to_append should be either empty or an absolute path.", to_append.empty() || to_append.is_absolute())); + if (std::filesystem::exists(to_append)) { + return_value.insert(return_value.begin(), to_append); + if (stop_after_first_result) { + break; + } + } + } + return return_value; +} + /** - * Tries to find a relative path inside of Base_directory. + * Tries to find a relative path inside of one of the Base_directories. * * @param relative_path A relative path that we’ll hopefully find in - * Base_directory. You don’t have to get the capitalization - * of relative_path correct, even on macOS and Linux. + * one of the Base_directories. You don’t have to get the + * capitalization of relative_path correct, even on macOS + * and Linux. * - * @return If the path is found, an absolute path that’s inside - * Base_directory. Otherwise, a path that probably doesn’t exist - * will be returned. + * @return Either an absolute path that’s inside a base directory or an empty + * path if nothing is found. */ 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); + auto return_value_list = cf_LocatePathMultiplePathsHelper(relative_path, true); + if (return_value_list.empty()) { + return ""; + } else { + return return_value_list.front(); + } +} + +/** + * Tries to find multiple relative paths inside of the Base_directories. + * + * @param relative_path A relative path that we’ll hopefully find in + * one or more of the Base_directories. You don’t have to + * get the capitalization of relative_path correct, even on + * macOS and Linux. + * + * @return A list of absolute paths. Each path will be inside one of the + * Base_directories. + */ +std::vector cf_LocateMultiplePaths(const std::filesystem::path &relative_path) { + return cf_LocatePathMultiplePathsHelper(relative_path, false); +} + +/* Not all Base_directories are necessarily writable, but this function will + * return one that should be writable. + */ +std::filesystem::path cf_GetWritableBaseDirectory() { + return Base_directories.front(); } // Generates a cfile error @@ -154,8 +219,8 @@ 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 - path to the HOG file, relative to Base_directory. -// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directory +// Parameters: libname - path to the HOG file, relative to one of the Base_directories. +// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directories // 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) { diff --git a/cfile/cfile.h b/cfile/cfile.h index e49ea579b..427964281 100644 --- a/cfile/cfile.h +++ b/cfile/cfile.h @@ -140,31 +140,63 @@ enum CFileExitStatus { CFES_IN_LIBRARY, }; -// The "root" directory of the D3 file tree -extern std::filesystem::path Base_directory; +/* The "root" directories of the D3 file tree + * + * Directories that come later in the list override directories that come + * earlier in the list. For example, if Base_directories[0] / "d3.hog" exists + * and Base_directories[1] / "d3.hog" also exists, then the one in + * Base_directories[1] will get used. The one in Base_directories[0] will be + * ignored. + */ +extern std::vector Base_directories; /* This function should be called at least once before you use anything else * from this module. */ -void cf_SetBaseDirectory(const std::filesystem::path &base_directory); +void cf_AddBaseDirectory(const std::filesystem::path &base_directory); + +/* After you call this function, you must call cf_AddBaseDirectory() at least + * once before you use anything else from this module. + */ +void cf_ClearBaseDirectories(); /** - * Tries to find a relative path inside of Base_directory. + * Tries to find a relative path inside of one of the Base_directories. * * @param relative_path A relative path that we’ll hopefully find in - * Base_directory. You don’t have to get the capitalization - * of relative_path correct, even on macOS and Linux. + * one of the Base_directories. You don’t have to get the + * capitalization of relative_path correct, even on macOS + * and Linux. * - * @return An absolute path that’s inside Base_directory. + * @return Either an absolute path that’s inside Base_directory or an empty path + * if nothing is found. */ std::filesystem::path cf_LocatePath(const std::filesystem::path &relative_path); +/** + * Tries to find multiple relative paths inside of the Base_directories. + * + * @param relative_path A relative path that we’ll hopefully find in + * one or more of the Base_directories. You don’t have to + * get the capitalization of relative_path correct, even on + * macOS and Linux. + * + * @return A list of absolute paths. Each path will be inside one of the + * Base_directories. + */ +std::vector cf_LocateMultiplePaths(const std::filesystem::path &relative_path); + +/* Not all Base_directories are necessarily writable, but this function will + * return one that should be writable. + */ +std::filesystem::path cf_GetWritableBaseDirectory(); + // 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 - path to the HOG file, relative to Base_directory. -// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directory +// Parameters: libname - path to the HOG file, relative to one of the Base_directories. +// NOTE: libname must be valid for the entire execution of the program. Therefore, Base_directories // 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); diff --git a/cfile/tests/cfile_tests.cpp b/cfile/tests/cfile_tests.cpp index 3ca94f0d0..c2b046878 100644 --- a/cfile/tests/cfile_tests.cpp +++ b/cfile/tests/cfile_tests.cpp @@ -22,12 +22,12 @@ #include #include "cfile.h" -void set_base_directory_to_cwd() { - cf_SetBaseDirectory(std::filesystem::current_path()); +void add_cwd_to_base_directories() { + cf_AddBaseDirectory(std::filesystem::current_path()); } TEST(D3, CFileIO) { - set_base_directory_to_cwd(); + add_cwd_to_base_directories(); int lib_handle = cf_OpenLibrary("TestDir/test.hog"); CFILE *file_handle = cfopen("lowercase.txt", "rb"); char buf[5]; @@ -48,7 +48,7 @@ TEST(D3, CFileIO) { } TEST(D3, CFileLibrary) { - set_base_directory_to_cwd(); + add_cwd_to_base_directories(); // First pass - without search path in "TestDir" (i.e. not search actual files in directory) // Second pass - with search path (files in directory goes first) for (int i = 0; i < 2; i++) { @@ -93,7 +93,8 @@ TEST(D3, CFileLocatePath) { for (auto const &item : test_paths) { auto directory = cwd / item.parent_path(); - cf_SetBaseDirectory(directory); + cf_ClearBaseDirectories(); + cf_AddBaseDirectory(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); diff --git a/ddio/sdlcontroller.cpp b/ddio/sdlcontroller.cpp index f8101d120..50b70b182 100644 --- a/ddio/sdlcontroller.cpp +++ b/ddio/sdlcontroller.cpp @@ -1397,93 +1397,97 @@ int CTLLex(const char *command) { return INFFILE_ERROR; } -// okay, now search for a '****.ctl' file in the current directory. +// okay, now search for a '****.ctl' file in the Base_directories void sdlgameController::parse_ctl_file(int devnum, const char *ctlname) { - // parse each file until we find a name match, no name match, just return - ddio_DoForeachFile( - Base_directory, std::regex(".*\\.ctl"), [this, &devnum, &ctlname](const std::filesystem::path &path) { - InfFile file; - bool found_name = false; - - if (file.Open(path.filename(), "[controller settings]", CTLLex)) { - // parse each line, setting the appropriate values, etc. - while (file.ReadLine()) { - int cmd; - char operand[128]; - - while ((cmd = file.ParseLine(operand, INFFILE_LINELEN)) > INFFILE_ERROR) { - // we want to assert that the name command comes before any other to verify - // this is the file we really want to change. - switch (cmd) { - case CTLCMD_NAME: - if (strcmp(ctlname, operand) != 0) - goto cancel_file_parse; - found_name = true; - break; - - case CTLCMD_DEAD: // deadzone - if (!found_name) - goto cancel_file_parse; - else { - m_ControlList[devnum].deadzone = atof(operand); - } - break; - - case CTLCMD_AXIS: // allowable axis. - // format of command is "+Z-R" - // this would add a Z axis to the controller. -R would remove the Rudder. - // you can do this for X,Y,Z,R,U,V. - if (!found_name) - goto cancel_file_parse; - else { - int slen = strlen(operand); - for (int i = 0; i <= slen; i += 2) { - int axis_flag; - if ((i + 1) <= slen) { - char axis_cmd = tolower(operand[i + 1]); - if (axis_cmd == 'x') - axis_flag = CTF_X_AXIS; - else if (axis_cmd == 'y') - axis_flag = CTF_Y_AXIS; - else if (axis_cmd == 'z') - axis_flag = CTF_Z_AXIS; - else if (axis_cmd == 'r') - axis_flag = CTF_R_AXIS; - else if (axis_cmd == 'u') - axis_flag = CTF_U_AXIS; - else if (axis_cmd == 'v') - axis_flag = CTF_V_AXIS; - else - axis_flag = 0; - if (operand[i] == '+') { - m_ControlList[devnum].flags |= axis_flag; - } else if (operand[i] == '-') { - m_ControlList[devnum].flags &= (~axis_flag); + for (auto base_directories_iterator = Base_directories.rbegin(); + base_directories_iterator != Base_directories.rend(); + ++base_directories_iterator) { + // parse each file until we find a name match, no name match, just return + ddio_DoForeachFile( + *base_directories_iterator, std::regex(".*\\.ctl"), [this, &devnum, &ctlname](const std::filesystem::path &path) { + InfFile file; + bool found_name = false; + + if (file.Open(path.filename(), "[controller settings]", CTLLex)) { + // parse each line, setting the appropriate values, etc. + while (file.ReadLine()) { + int cmd; + char operand[128]; + + while ((cmd = file.ParseLine(operand, INFFILE_LINELEN)) > INFFILE_ERROR) { + // we want to assert that the name command comes before any other to verify + // this is the file we really want to change. + switch (cmd) { + case CTLCMD_NAME: + if (strcmp(ctlname, operand) != 0) + goto cancel_file_parse; + found_name = true; + break; + + case CTLCMD_DEAD: // deadzone + if (!found_name) + goto cancel_file_parse; + else { + m_ControlList[devnum].deadzone = atof(operand); + } + break; + + case CTLCMD_AXIS: // allowable axis. + // format of command is "+Z-R" + // this would add a Z axis to the controller. -R would remove the Rudder. + // you can do this for X,Y,Z,R,U,V. + if (!found_name) + goto cancel_file_parse; + else { + int slen = strlen(operand); + for (int i = 0; i <= slen; i += 2) { + int axis_flag; + if ((i + 1) <= slen) { + char axis_cmd = tolower(operand[i + 1]); + if (axis_cmd == 'x') + axis_flag = CTF_X_AXIS; + else if (axis_cmd == 'y') + axis_flag = CTF_Y_AXIS; + else if (axis_cmd == 'z') + axis_flag = CTF_Z_AXIS; + else if (axis_cmd == 'r') + axis_flag = CTF_R_AXIS; + else if (axis_cmd == 'u') + axis_flag = CTF_U_AXIS; + else if (axis_cmd == 'v') + axis_flag = CTF_V_AXIS; + else + axis_flag = 0; + if (operand[i] == '+') { + m_ControlList[devnum].flags |= axis_flag; + } else if (operand[i] == '-') { + m_ControlList[devnum].flags &= (~axis_flag); + } else { + goto cancel_file_parse; + } } else { - goto cancel_file_parse; + break; // this should break out of the axis search but continue with the file } - } else { - break; // this should break out of the axis search but continue with the file } } + break; + + case CTLCMD_SX: // allow modification of global sensitivity modifiers + case CTLCMD_SY: + case CTLCMD_SZ: + case CTLCMD_SR: + case CTLCMD_SU: + case CTLCMD_SV: { + int idx = (cmd - CTLCMD_SX); + m_ControlList[devnum].sensmod[idx] = atof(operand); + break; + } } - break; - - case CTLCMD_SX: // allow modification of global sensitivity modifiers - case CTLCMD_SY: - case CTLCMD_SZ: - case CTLCMD_SR: - case CTLCMD_SU: - case CTLCMD_SV: { - int idx = (cmd - CTLCMD_SX); - m_ControlList[devnum].sensmod[idx] = atof(operand); - break; - } } } + cancel_file_parse: + file.Close(); } - cancel_file_parse: - file.Close(); - } - }); + }); + } } diff --git a/editor/MainFrm.cpp b/editor/MainFrm.cpp index 2334e3da9..009fce25a 100644 --- a/editor/MainFrm.cpp +++ b/editor/MainFrm.cpp @@ -1460,7 +1460,7 @@ void InitCScripts() { CreateNewMine(); // Setup include directories for OSIRIS - ddio_MakePath(path, Base_directory.u8string().c_str(), "data", "levels", NULL); + ddio_MakePath(path, cf_GetWritableBaseDirectory().u8string().c_str(), "data", "levels", NULL); } // Copied from winmain.cpp diff --git a/editor/editor_lighting.cpp b/editor/editor_lighting.cpp index 5f90fffd0..5af799ec2 100644 --- a/editor/editor_lighting.cpp +++ b/editor/editor_lighting.cpp @@ -870,7 +870,7 @@ void DoRadiosityForRooms() { if (save_after_bsp) { char filename[_MAX_PATH]; - ddio_MakePath(filename, Base_directory.u8string().c_str(), "BSPSave.D3L", NULL); + ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "BSPSave.D3L", NULL); // Save the level to SaveLevel(filename); @@ -1140,7 +1140,7 @@ void DoRadiosityForRooms() { SqueezeLightmaps(0, -1); char filename[_MAX_PATH + 1]; - ddio_MakePath(filename, Base_directory.u8string().c_str(), "LightSave.D3L", NULL); + ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "LightSave.D3L", NULL); // Save the level to disk SaveLevel(filename); diff --git a/editor/gameeditor.cpp b/editor/gameeditor.cpp index d36268a33..efbb05cad 100644 --- a/editor/gameeditor.cpp +++ b/editor/gameeditor.cpp @@ -611,7 +611,7 @@ void GameToEditor(bool set_viewer_from_player) { if (Temp_level_saved) { char filename[_MAX_PATH]; - ddio_MakePath(filename, Base_directory.u8string().c_str(), "GameSave.D3L", NULL); // make explicit path + ddio_MakePath(filename, cf_GetWritableBaseDirectory().u8string().c_str(), "GameSave.D3L", NULL); // make explicit path LoadLevel(filename); Temp_level_saved = 0; } @@ -744,7 +744,7 @@ void EditorToGame() { // set game working directory bool set_size = false; ddio_GetWorkingDir(Editor_dir, sizeof(Editor_dir)); - ddio_SetWorkingDir(Base_directory.u8string().c_str()); + ddio_SetWorkingDir(cf_GetWritableBaseDirectory().u8string().c_str()); Osiris_ResetAllTimers(); diff --git a/manage/manage.cpp b/manage/manage.cpp index 1829baddc..b5429d334 100644 --- a/manage/manage.cpp +++ b/manage/manage.cpp @@ -673,11 +673,11 @@ int mng_LoadTableFiles(int show_progress) { // the user doesn't want network support int mng_InitLocalTables() { // Set the local table directory from the base directory. - auto base_directory_string = Base_directory.u8string(); - strncpy(LocalD3Dir, base_directory_string.c_str(), sizeof LocalD3Dir); + auto writable_base_directory_string = cf_GetWritableBaseDirectory().u8string(); + strncpy(LocalD3Dir, writable_base_directory_string.c_str(), sizeof LocalD3Dir); LocalD3Dir[sizeof LocalD3Dir - 1] = '\0'; - if (strlen(LocalD3Dir) != strlen(base_directory_string.c_str())) { - LOG_WARNING << "Base_directory is too long to fit in LocalD3Dir, so LocalD3Dir was truncated."; + if (strlen(LocalD3Dir) != strlen(writable_base_directory_string.c_str())) { + LOG_WARNING << "cf_GetWritableBaseDirectory() is too long to fit in LocalD3Dir, so LocalD3Dir was truncated."; } LOG_INFO << "Local dir: " << LocalD3Dir; diff --git a/netcon/includes/con_dll.h b/netcon/includes/con_dll.h index 374217981..9cc7cf963 100644 --- a/netcon/includes/con_dll.h +++ b/netcon/includes/con_dll.h @@ -701,6 +701,15 @@ typedef void (*ddio_DoForeachFile_fp)(const std::filesystem::path &search_path, const std::function &func); ddio_DoForeachFile_fp DLLddio_DoForeachFile; +typedef decltype(&cf_LocatePath) cf_LocatePath_fp; +cf_LocatePath_fp DLLcf_LocatePath; + +typedef decltype(&cf_LocateMultiplePaths) cf_LocateMultiplePaths_fp; +cf_LocateMultiplePaths_fp DLLcf_LocateMultiplePaths; + +typedef decltype(&cf_GetWritableBaseDirectory) cf_GetWritableBaseDirectory_fp; +cf_GetWritableBaseDirectory_fp DLLcf_GetWritableBaseDirectory; + int DLLUIClass_CurrID = 0xD0; #define MAX_NET_GAMES 100 @@ -768,7 +777,6 @@ int DLLGame_mode; char *DLLTracker_id; int *DLLNum_directplay_games; netgame_info *DLLNetgame; -std::filesystem::path *DLLLocalD3Dir; int *DLLMultiGameStarting; netplayer *DLLMNetPlayers; int MTWritingPilot, MTReadingPilot; @@ -998,13 +1006,15 @@ int StartMultiplayerGameMenu() { DLLListRemoveAll(script_list); #if (!(defined(OEM) || defined(DEMO))) - DLLddio_DoForeachFile(*DLLLocalD3Dir / "netgames", std::regex(".+\\.d3m"), - [&dll_ui_items](const std::filesystem::path& path){ - dll_ui_items.insert_or_assign( - path.stem().u8string(), - DLLCreateNewUITextItem(path.stem().u8string().c_str(), UICOL_LISTBOX_LO, -1) - ); - } ); + for (const auto &netgames_directory : DLLcf_LocateMultiplePaths("netgames")) { + DLLddio_DoForeachFile(netgames_directory, std::regex(".+\\.d3m"), + [&dll_ui_items](const std::filesystem::path& path){ + dll_ui_items.insert_or_assign( + path.stem().u8string(), + DLLCreateNewUITextItem(path.stem().u8string().c_str(), UICOL_LISTBOX_LO, -1) + ); + } ); + } #else dll_ui_items.insert_or_assign("Anarchy", DLLCreateNewUITextItem("Anarchy", UICOL_LISTBOX_LO, -1)); dll_ui_items.insert_or_assign("Capture The Flag", DLLCreateNewUITextItem("Capture The Flag", UICOL_LISTBOX_LO, -1)); @@ -1017,10 +1027,13 @@ int StartMultiplayerGameMenu() { #if (!(defined(OEM) || defined(DEMO))) msn_list *mi; - const std::vector> search_paths = { - {*DLLLocalD3Dir / "data" / "levels", std::regex(".+\\.msn")}, - {*DLLLocalD3Dir / "missions", std::regex(".+\\.mn3")} - }; + std::vector> search_paths = { }; + for (const auto &levels_directory : DLLcf_LocateMultiplePaths(std::filesystem::path("data") / "levels")) { + search_paths.push_back({levels_directory, std::regex(".+\\.msn")}); + } + for (const auto &missions_directory : DLLcf_LocateMultiplePaths("missions")) { + search_paths.push_back({missions_directory, std::regex(".+\\.mn3")}); + } for (auto const &i : search_paths) { DLLddio_DoForeachFile(i.first, i.second, [&mi, &list_1](const std::filesystem::path &path) { @@ -1074,7 +1087,7 @@ int StartMultiplayerGameMenu() { DLLNetgame->flags = NF_RANDOMIZE_RESPAWN; DLLNewUIWindowLoadBackgroundImage(main_wnd, "multimain.ogf"); DLLNewUIWindowOpen(main_wnd); - if (DLLMultiLoadSettings(*DLLLocalD3Dir / "custom" / "settings" / "default.mps")) { + if (DLLMultiLoadSettings(DLLcf_GetWritableBaseDirectory() / "custom" / "settings" / "default.mps")) { DLLEditSetText(mission_name_edit, DLLNetgame->name); #if (!(defined(OEM) || defined(DEMO))) p = DLLGetMissionName(DLLNetgame->mission); diff --git a/netcon/includes/mdllinit.h b/netcon/includes/mdllinit.h index b563ff63d..676b250be 100644 --- a/netcon/includes/mdllinit.h +++ b/netcon/includes/mdllinit.h @@ -275,6 +275,9 @@ DLLShowNetgameInfo = (ShowNetgameInfo_fp)API.fp[108]; // API.fp[109]; // Not used DLLCheckGetD3M = (CheckGetD3M_fp)API.fp[110]; DLLddio_DoForeachFile = (ddio_DoForeachFile_fp)API.fp[111]; +DLLcf_LocatePath = (cf_LocatePath_fp)API.fp[112]; +DLLcf_LocateMultiplePaths = (cf_LocateMultiplePaths_fp)API.fp[113]; +DLLcf_GetWritableBaseDirectory = (cf_GetWritableBaseDirectory_fp)API.fp[114]; DLLMPlayers = (player *)API.players; DLLNetgame = (netgame_info *)API.netgame; @@ -286,7 +289,7 @@ DLLTracker_id = (char *)API.vp[1]; DLLGame_is_master_tracker_game = API.vp[2]; DLLGame_mode = *API.vp[3]; // DLLCurrent_pilot = (pilot *)API.vp[4]; -DLLLocalD3Dir = (std::filesystem::path *)API.vp[5]; +// DLLLocalD3Dir = (std::filesystem::path *)API.vp[5]; DLLMultiGameStarting = (int *)API.vp[6]; DLLMTPilotinfo = (vmt_descent3_struct *)API.vp[7]; DLLNum_network_games_known = API.vp[8]; diff --git a/netcon/mtclient/mtclient.cpp b/netcon/mtclient/mtclient.cpp index dfdcfbf07..bb30d5d7a 100644 --- a/netcon/mtclient/mtclient.cpp +++ b/netcon/mtclient/mtclient.cpp @@ -2132,7 +2132,7 @@ void CheckPXOForAnomalies() { if (stricmp(DLLMPlayers[i].tracker_id, DLLMPlayers[j].tracker_id) == 0) { // Ok, what we have here is multiple users with the same tracker ID. // This is bad. It could be user error, but it could be something worse. - std::filesystem::path errfilepath = *DLLLocalD3Dir / "pxo.err"; + std::filesystem::path errfilepath = DLLcf_GetWritableBaseDirectory() / "pxo.err"; FILE *errfile = fopen(errfilepath.u8string().c_str(), "at"); if (errfile) { fprintf(errfile, "Dup TID: %s & %s / %s\n", DLLMPlayers[j].callsign, DLLMPlayers[i].callsign,