#include #include #include #include #include "modpath_handler.h" #include "config.h" #include "utils.h" using std::nullopt; namespace layeredfs { typedef struct { std::string name; std::unordered_set contents; } mod_contents_t; std::vector cached_mods; std::unordered_set walk_dir(const string &path, const string &root) { std::unordered_set result; WIN32_FIND_DATAA ffd; auto contents = FindFirstFileA((path + "/*").c_str(), &ffd); if (contents != INVALID_HANDLE_VALUE) { do { if (!strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, "..")) { continue; } string result_path; if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { result_path = root + ffd.cFileName + "/"; logf_verbose(" %s", result_path.c_str()); auto subdir_walk = walk_dir(path + "/" + ffd.cFileName, result_path); result.insert(subdir_walk.begin(), subdir_walk.end()); } else { result_path = root + ffd.cFileName; logf_verbose(" %s", result_path.c_str()); } result.insert(result_path); } while (FindNextFileA(contents, &ffd) != 0); FindClose(contents); } return result; } void cache_mods(void) { if (config.developer_mode) return; // this is a bit hacky config.developer_mode = true; auto avail_mods = available_mods(); config.developer_mode = false; for (auto &dir : avail_mods) { logf_verbose("Walking %s", dir.c_str()); mod_contents_t mod; mod.name = dir; mod.contents = walk_dir(dir, ""); cached_mods.push_back(mod); } } optional normalise_path(const string &path) { auto data_pos = path.find("data/"); auto data2_pos = string::npos; if (data_pos == string::npos) { data2_pos = path.find("data2/"); if (data2_pos == string::npos) return nullopt; } auto actual_pos = (data_pos != string::npos) ? data_pos : data2_pos; // if data2 was found, use root data2/.../... instead of just .../... auto offset = (data2_pos != string::npos) ? 0 : strlen("data/"); auto data_str = path.substr(actual_pos + offset); // nuke backslash string_replace(data_str, "\\", "/"); // nuke double slash string_replace(data_str, "//", "/"); return data_str; } vector available_mods() { vector ret; string mod_root = MOD_FOLDER "/"; if (config.developer_mode) { WIN32_FIND_DATAA ffd; auto mods = FindFirstFileA(MOD_FOLDER "/*", &ffd); if (mods != INVALID_HANDLE_VALUE) { do { if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || !strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, "..") || !strcmp(ffd.cFileName, "_cache")) { continue; } ret.push_back(mod_root + ffd.cFileName); } while (FindNextFileA(mods, &ffd) != 0); FindClose(mods); } } else { for (auto &dir : cached_mods) { ret.push_back(dir.name); } } std::sort(ret.begin(), ret.end()); return ret; } bool mkdir_p(string &path) { /* Adapted from http://stackoverflow.com/a/2336245/119527 */ const size_t len = strlen(path.c_str()); char _path[MAX_PATH + 1]; char *p; errno = 0; /* Copy string so its mutable */ if (len > sizeof(_path) - 1) { return false; } strncpy(_path, path.c_str(), MAX_PATH); _path[MAX_PATH] = '\0'; /* Iterate the string */ for (p = _path + 1; *p; p++) { if (*p == '/') { /* Temporarily truncate */ *p = '\0'; if (!CreateDirectoryA(_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { return false; } *p = '/'; } } if (!CreateDirectoryA(_path, NULL)) { if (GetLastError() != ERROR_ALREADY_EXISTS) { return false; } } return true; } // same for files and folders when cached optional find_first_cached_item(const string &norm_path) { for (auto &dir : cached_mods) { auto file_search = dir.contents.find(norm_path); if (file_search == dir.contents.end()) { continue; } return dir.name + "/" + *file_search; } return nullopt; } optional find_first_modfile(const string &norm_path) { if (config.developer_mode) { for (auto &dir : available_mods()) { auto mod_path = dir + "/" + norm_path; if (file_exists(mod_path.c_str())) { return mod_path; } } } else { return find_first_cached_item(norm_path); } return nullopt; } optional find_first_modfolder(const string &norm_path) { if (config.developer_mode) { for (auto &dir : available_mods()) { auto mod_path = dir + "/" + norm_path; if (folder_exists(mod_path.c_str())) { return mod_path; } } } else { return find_first_cached_item(norm_path + "/"); } return nullopt; } vector find_all_modfile(const string &norm_path) { vector ret; if (config.developer_mode) { for (auto &dir : available_mods()) { auto mod_path = dir + "/" + norm_path; if (file_exists(mod_path.c_str())) { ret.push_back(mod_path); } } } else { for (auto &dir : cached_mods) { auto file_search = dir.contents.find(norm_path); if (file_search == dir.contents.end()) { continue; } ret.push_back(dir.name + "/" + *file_search); } } // needed for consistency when hashing names std::sort(ret.begin(), ret.end()); return ret; } }