#include "filesystem.h" #include "file_inzip.h" #include "file_mtp.h" #include "file_nfs.h" #include "file_permanent.h" #include "../lib_print.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #include #include #include #include #include namespace { bool PrefixNoCase(const char* text, const char* prefix) { if (text == nullptr || prefix == nullptr) return false; return _strnicmp(text, prefix, std::strlen(prefix)) == 0; } bool EqualNoCase(const char* left, const char* right) { return _stricmp(left == nullptr ? "" : left, right == nullptr ? "" : right) == 0; } bool IsOSPathRecovered(const char* path) { if (path == nullptr || path[0] == '\0') return false; static const char* const prefixes[] = { "mtp:", "nfs:", "devkit:", "game:", "cache:", "dlc:", "saves:" }; for (const char* prefix : prefixes) { if (PrefixNoCase(path, prefix)) return true; } return ((std::isalpha(static_cast(path[0])) != 0) && path[1] == ':') || path[0] == '\\' || path[0] == '/'; } bool IsNativeOSPath(const char* path) { return IsOSPathRecovered(path) && !PrefixNoCase(path, "mtp:") && !PrefixNoCase(path, "nfs:"); } std::string SlashesToWindows(std::string path) { std::replace(path.begin(), path.end(), '/', '\\'); return path; } std::string SlashesToGame(std::string path) { std::replace(path.begin(), path.end(), '\\', '/'); return path; } std::string JoinPath(const char* base, const char* relativePath) { const char* relative = relativePath == nullptr ? "" : relativePath; if (IsOSPathRecovered(relative)) return SlashesToWindows(relative); std::string result = base == nullptr ? "" : base; if (!result.empty() && result.back() != '\\' && result.back() != '/') result.push_back('\\'); while (*relative == '\\' || *relative == '/') ++relative; result += relative; result = SlashesToWindows(result); if (IsNativeOSPath(result.c_str())) { char fullPath[32768] = {}; const DWORD amount = GetFullPathNameA(result.c_str(), static_cast(sizeof(fullPath)), fullPath, nullptr); if (amount > 0 && amount < sizeof(fullPath)) result.assign(fullPath); } return result; } std::string ParentPath(const std::string& path) { const std::size_t separator = path.find_last_of("\\/"); if (separator == std::string::npos) return std::string(); if (separator == 2 && path.size() >= 3 && path[1] == ':') return path.substr(0, 3); return path.substr(0, separator); } void CopyString(char* destination, const int destinationSize, const char* source) { if (destination == nullptr || destinationSize <= 0) return; _snprintf_s(destination, static_cast(destinationSize), _TRUNCATE, "%s", source == nullptr ? "" : source); } std::string ExecutableDirectory() { char path[32768] = {}; const DWORD amount = GetModuleFileNameA(nullptr, path, static_cast(sizeof(path))); if (amount == 0 || amount >= sizeof(path)) return "."; return ParentPath(path); } std::string EnvironmentPath(const char* name) { char value[32768] = {}; const DWORD amount = GetEnvironmentVariableA(name, value, static_cast(sizeof(value))); return amount > 0 && amount < sizeof(value) ? value : ""; } unsigned int NativeTimestamp(const char* path) { WIN32_FILE_ATTRIBUTE_DATA attributes = {}; if (!GetFileAttributesExA(path, GetFileExInfoStandard, &attributes)) return static_cast(-1); ULARGE_INTEGER value = {}; value.LowPart = attributes.ftLastWriteTime.dwLowDateTime; value.HighPart = attributes.ftLastWriteTime.dwHighDateTime; const std::uint64_t ticks = value.QuadPart > 116444736000000000ULL ? value.QuadPart - 116444736000000000ULL : 0; return static_cast(ticks / 10000000ULL); } bool IsDirectoryNative(const char* path) { const DWORD attributes = GetFileAttributesA(path); return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } bool ExtensionMatches(const char* name, const char* extension) { if (extension == nullptr || extension[0] == '\0') return true; const char* wanted = extension; while (*wanted == '*' || *wanted == '.') ++wanted; const char* actual = std::strrchr(name, '.'); if (actual == nullptr) return false; return EqualNoCase(actual + 1, wanted); } std::string WithExtension(std::string path, const char* extension) { const std::size_t separator = path.find_last_of("\\/"); const std::size_t dot = path.find_last_of('.'); if (dot != std::string::npos && (separator == std::string::npos || dot > separator)) { path.erase(dot); } if (extension != nullptr && extension[0] != '\0') { if (extension[0] != '.') path.push_back('.'); path += extension; } return path; } const char* StripRecoveredBasePrefix(const char* path) { if (path == nullptr) return ""; if (PrefixNoCase(path, "base/") || PrefixNoCase(path, "base\\")) return path + 5; if ((path[0] == '/' || path[0] == '\\') && (PrefixNoCase(path + 1, "base/") || PrefixNoCase(path + 1, "base\\"))) { return path + 6; } while ((path[0] == '/' && path[1] != '/') || (path[0] == '\\' && path[1] != '\\')) ++path; return path; } void ListNativeFiles(const char* directory, const char* extension, idList& list) { const std::string search = JoinPath(directory, "*"); WIN32_FIND_DATAA findData = {}; HANDLE handle = FindFirstFileA(search.c_str(), &findData); if (handle == INVALID_HANDLE_VALUE) return; const bool wantDirectories = extension != nullptr && extension[0] == '/' && extension[1] == '\0'; do { const bool isDirectory = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; if (wantDirectories != isDirectory) continue; if (!wantDirectories && !ExtensionMatches(findData.cFileName, extension)) continue; list.Append(idStr(findData.cFileName)); } while (FindNextFileA(handle, &findData)); FindClose(handle); } } // namespace idFileSystemLocal fileSystemLocal; idFileSystem* fileSystem = &fileSystemLocal; idFileSystem::~idFileSystem() = default; idFileList::idFileList() : basePath(), list(16) {} idCachedZipFile::idCachedZipFile() : zipFileName(), relativeZipFileName(), handle(nullptr), numFiles(0), fileList(nullptr), next(nullptr) { std::memset(hashTable, 0, sizeof(hashTable)); } idCachedZipFile::~idCachedZipFile() { delete[] fileList; fileList = nullptr; handle = nullptr; } unsigned int idFile_Stat::Read(void*, unsigned int) { idLibPrint::Error("Read is not allowed with idFile_Stat"); } unsigned int idFile_Stat::Write(const void*, unsigned int) { idLibPrint::Error("Write is not allowed with idFile_Stat"); } unsigned int idFile_Stat::ReadOfs(std::int64_t, void*, unsigned int) { idLibPrint::Error("ReadOfs is not allowed with idFile_Stat"); } unsigned int idFile_Stat::WriteOfs(std::int64_t, const void*, unsigned int) { idLibPrint::Error("WriteOfs is not allowed with idFile_Stat"); } void idFile_Stat::SetLength(unsigned int) { idLibPrint::Error("SetLength is not allowed with idFile_Stat"); } int idFile_Stat::Seek(std::int64_t, fsOrigin_t) { idLibPrint::Error("Seeking is not allowed with idFile_Stat"); } idFileSystemLocal::idFileSystemLocal() : searchPaths(16), cachedZipFiles(nullptr), loadCount(0), loadStack(0), pathBase(), pathCache(), pathInstall(), pathSave(), preCallback(nullptr), postCallback(nullptr), cacheStatCallback(nullptr), cacheCallback(nullptr), patchCallback(nullptr) {} idFileSystemLocal::~idFileSystemLocal() { ClearZipCache(); } bool idFileSystemLocal::CopyGameFile(idFile* source, idFile* destination) { if (source == nullptr || destination == nullptr) return false; unsigned char buffer[4096]; std::int64_t total = 0; for (;;) { const unsigned int amount = source->Read(buffer, sizeof(buffer)); if (amount == 0) break; if (destination->Write(buffer, amount) != amount) { idLibPrint::Warning("Write didn't match requested size"); return false; } total += amount; } source->Seek(0, FS_SEEK_SET); if (total != source->Length()) idLibPrint::Warning("Total bytes written doesn't match filesize"); return total == source->Length(); } int idFileSystemLocal::ReadFile(const char* relativePath, void** buffer, unsigned int* timestamp) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (relativePath == nullptr || relativePath[0] == '\0') { idLibPrint::Warning("idFileSystemLocal::ReadFile with empty name"); return -1; } if (timestamp != nullptr) *timestamp = static_cast(-1); if (buffer != nullptr) *buffer = nullptr; idFileLocal file(OpenFileRead(relativePath, buffer != nullptr, false)); if (file.file == nullptr) return -1; const std::int64_t fileLength = file->Length(); if (fileLength < 0 || fileLength > INT_MAX) return -1; const int length = static_cast(fileLength); if (timestamp != nullptr) *timestamp = file->Timestamp(); if (buffer != nullptr) { ++loadCount; ++loadStack; unsigned char* data = static_cast( std::malloc(static_cast(length) + 1)); if (data == nullptr) { --loadStack; return -1; } const unsigned int amount = file->Read(data, static_cast(length)); if (amount != static_cast(length)) { std::free(data); --loadStack; return -1; } data[length] = 0; *buffer = data; } return length; } void idFileSystemLocal::FreeFile(void* buffer) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (buffer == nullptr) idLibPrint::FatalError("idFileSystemLocal::FreeFile( NULL )"); --loadStack; std::free(buffer); } const char* idFileSystemLocal::GetBasePathStr(const fsPath_t basePath) const { switch (basePath) { case FSPATH_BASE: return pathBase.c_str(); case FSPATH_CACHE: if (pathCache.Length() > 0) return pathCache.c_str(); // Recovered code deliberately falls through to the save path. case FSPATH_SAVE: return pathSave.Length() > 0 ? pathSave.c_str() : pathBase.c_str(); case FSPATH_INSTALL: return pathInstall.c_str(); default: return ""; } } void idFileSystemLocal::GetNumberedFilename(int& lastNumber, const char* base, const char* extension, idStr& outputFilename, const bool overrideProduction) { if (++lastNumber > 99999) lastNumber = 99999; while (lastNumber < 99999) { outputFilename.Format("%s%05i.%s", base == nullptr ? "" : base, lastNumber, extension == nullptr ? "" : extension); if (!FileExists(outputFilename.c_str(), overrideProduction)) break; ++lastNumber; } } bool idFileSystemLocal::IsInitialized() const { return searchPaths.Num() > 0; } idFile* idFileSystemLocal::OpenFromMTP(const char* path, fsMode_t mode) { idFile_MTP* file = new idFile_MTP(); const char* remotePath = PrefixNoCase(path, "mtp:") ? path + 4 : path; if (file->Open(remotePath, mode)) return file; delete file; return nullptr; } idFile* idFileSystemLocal::OpenFromNFS(const char* path, fsMode_t mode, bool create) { idFile_Nfs* file = new idFile_Nfs(); if (file->Open(path, mode, create, create)) return file; delete file; return nullptr; } bool idFileSystemLocal::IsRelativePath(const char* path) const { return !IsOSPathRecovered(path); } void idFileSystemLocal::CreateOSPath(const char* osPath) { if (osPath == nullptr || osPath[0] == '\0') return; if (PrefixNoCase(osPath, "nfs:")) { idFile_Nfs::CreateOsPath(osPath); return; } if (!IsNativeOSPath(osPath)) return; std::string path = SlashesToWindows(osPath); for (std::size_t index = 1; index < path.size(); ++index) { if (path[index] != '\\') continue; if (index == 2 && path[1] == ':') continue; if (index == 1 && path[0] == '\\') continue; const char saved = path[index]; path[index] = '\0'; if (path[0] != '\0') CreateDirectoryA(path.c_str(), nullptr); path[index] = saved; } } idStr idFileSystemLocal::BuildOSPath(const char* base, const char* relativePath) const { return idStr(JoinPath(base, relativePath).c_str()); } idStr idFileSystemLocal::BuildOSPath(const fsPath_t basePath, const char* relativePath) const { const std::string gameRoot = JoinPath(GetBasePathStr(basePath), "base"); return idStr(JoinPath(gameRoot.c_str(), relativePath).c_str()); } idStr idFileSystemLocal::GeneratedPath(const char* appendedPath) const { const std::string generated = JoinPath("generated", appendedPath); return idStr(SlashesToGame(generated).c_str()); } bool idFileSystemLocal::FixLongFilename(const char* basePath, const char* extension, const char* inputPath, char* fixedPath, const int fixedPathSize) const { if (fixedPath == nullptr || fixedPathSize <= 0) return false; const char* relative = inputPath == nullptr ? "" : inputPath; if (PrefixNoCase(relative, "../../")) relative += 6; std::string input = WithExtension(SlashesToGame(relative), extension); const std::size_t slash = input.find_last_of('/'); const std::string directory = slash == std::string::npos ? "" : input.substr(0, slash + 1); std::string filename = slash == std::string::npos ? input : input.substr(slash + 1); const std::size_t dot = filename.find_last_of('.'); const std::string suffix = dot == std::string::npos ? "" : filename.substr(dot); std::string stem = dot == std::string::npos ? filename : filename.substr(0, dot); std::string recoveredPath = directory; if (filename.size() >= 38) { while (stem.size() > 30) { recoveredPath += stem.substr(0, 30); recoveredPath.push_back('/'); stem.erase(0, 30); } } recoveredPath += stem + suffix; const std::string result = JoinPath(basePath, recoveredPath.c_str()); CopyString(fixedPath, fixedPathSize, result.c_str()); return true; } bool idFileSystemLocal::OSPathToRelativePath(const char* osPath, char* relativePath, const int relativePathSize) { if (relativePath == nullptr || relativePathSize <= 0) return false; relativePath[0] = '\0'; if (osPath == nullptr) return false; for (int index = 0; index < searchPaths.Num(); ++index) { const std::string root = SlashesToWindows(searchPaths[index].c_str()); const std::string candidate = SlashesToWindows(osPath); if (_strnicmp(root.c_str(), candidate.c_str(), root.size()) != 0) continue; const char* suffix = candidate.c_str() + root.size(); while (*suffix == '\\' || *suffix == '/') ++suffix; const std::string gamePath = SlashesToGame(suffix); CopyString(relativePath, relativePathSize, gamePath.c_str()); return true; } idLibPrint::Warning("idFileSystem::OSPathToRelativePath failed on %s", osPath); return false; } void idFileSystemLocal::RelativePathToOSPath(const char* relativePath, char* osPath, const int osPathSize, const fsPath_t basePath) const { const idStr result = BuildOSPath(basePath, relativePath); CopyString(osPath, osPathSize, result.c_str()); } idStr idFileSystemLocal::RelativePathToOSPath(const char* relativePath, const fsPath_t basePath) const { return BuildOSPath(basePath, relativePath); } bool idFileSystemLocal::RemoveDir(const char* relativePath) { bool result = true; if (pathSave.Length() > 0) { const idStr savePath = BuildOSPath(FSPATH_SAVE, relativePath); result = PrefixNoCase(savePath.c_str(), "nfs:") ? idFile_Nfs::RemoveFile(savePath.c_str()) : RemoveDirectoryA(savePath.c_str()) != FALSE; } const idStr basePath = BuildOSPath(FSPATH_BASE, relativePath); const bool baseResult = PrefixNoCase(basePath.c_str(), "nfs:") ? idFile_Nfs::RemoveFile(basePath.c_str()) : RemoveDirectoryA(basePath.c_str()) != FALSE; return result && baseResult; } bool idFileSystemLocal::RenameFile(const char* relativePath, const char* newName, const fsPath_t basePath) { const idStr oldPath = BuildOSPath(basePath, relativePath); const idStr newPath = BuildOSPath(basePath, newName); if (PrefixNoCase(relativePath, "nfs:")) return idFile_Nfs::RenameFile(oldPath.c_str(), newPath.c_str()); if (MoveFileExA(oldPath.c_str(), newPath.c_str(), MOVEFILE_REPLACE_EXISTING) != FALSE) return true; idLibPrint::Warning("RenameFile( %s, %s ) error %lu", newPath.c_str(), oldPath.c_str(), GetLastError()); return false; } bool idFileSystemLocal::IsWritable(const char* relativePath) const { const idStr path = BuildOSPath(FSPATH_BASE, relativePath); const DWORD attributes = GetFileAttributesA(path.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES) return (attributes & FILE_ATTRIBUTE_READONLY) == 0; const std::string parent = ParentPath(path.c_str()); const DWORD parentAttributes = GetFileAttributesA(parent.c_str()); return parentAttributes != INVALID_FILE_ATTRIBUTES && (parentAttributes & FILE_ATTRIBUTE_READONLY) == 0; } sysFolder_t idFileSystemLocal::IsFolder(const char* relativePath, const fsPath_t basePath) const { const idStr path = BuildOSPath(basePath, relativePath); const DWORD attributes = GetFileAttributesA(path.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) return FOLDER_ERROR; return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? FOLDER_YES : FOLDER_NO; } void idFileSystemLocal::ListOSFiles(const char* directory, const char* extension, idList& list) { const char* const filter = extension == nullptr ? "" : extension; if (PrefixNoCase(directory, "nfs:")) { idFile_Nfs::ListFiles(directory, filter, list); } else if (PrefixNoCase(directory, "mtp:")) { idFile_MTP file; file.List(directory, filter, list); } else { ListNativeFiles(directory, filter, list); } } idCachedZipFile* idFileSystemLocal::LoadZipFile(const char*, const char*) { // The recovered implementation is activated with recovered unzip.cpp. return nullptr; } std::int64_t idFileSystemLocal::GetFileLength(const char* relativePath) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization"); if (relativePath == nullptr || relativePath[0] == '\0') { idLibPrint::Warning("idFileSystemLocal::GetFileLength with empty name"); return -1; } idFileLocal file(OpenFileRead(relativePath, false, false)); return file.file == nullptr ? -1 : file->Length(); } idFile* idFileSystemLocal::OpenFileAppend(const char* relativePath, const fsPath_t basePath) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); const idStr osPath = BuildOSPath(basePath, relativePath); if (PrefixNoCase(osPath.c_str(), "nfs:")) return OpenFromNFS(osPath.c_str(), FS_APPEND, true); if (PrefixNoCase(osPath.c_str(), "mtp:")) return OpenFromMTP(osPath.c_str(), FS_APPEND); CreateOSPath(osPath.c_str()); idFile_Permanent* file = new idFile_Permanent(relativePath, osPath.c_str(), FS_APPEND, false); if (file->IsOpen()) return file; delete file; return nullptr; } void idFileSystemLocal::SetFilePreCallback(filePreCallback_t callback) { preCallback = callback; } void idFileSystemLocal::SetFilePostCallback(filePostCallback_t callback) { postCallback = callback; } void idFileSystemLocal::SetFileCacheCallback(fileCacheCallback_t statCallback, fileCacheCallback_t callback) { cacheStatCallback = statCallback; cacheCallback = callback; } void idFileSystemLocal::SetFilePatchCallback(filePatchCallback_t callback) { patchCallback = callback; } bool idFileSystemLocal::CopyGameFile(const char* from, const char* to, const fsPath_t basePath) { idFileLocal source(OpenFileRead(from, false, false)); idFileLocal destination(OpenFileWritePermanent(to, basePath)); return source.file != nullptr && destination.file != nullptr && CopyGameFile(source.file, destination.file); } idFile_InZip* idFileSystemLocal::ReadFileFromZip(idCachedZipFile*, const char*, bool) { // The recovered implementation is activated with recovered file_inzip.cpp. return nullptr; } idFile* idFileSystemLocal::OpenFileRead(const char* relativePath, const bool allowCopyFiles, const bool uncompressedOnly) { if (patchCallback != nullptr) { idFile* patched = patchCallback(relativePath); if (patched != nullptr) return patched; } if (preCallback != nullptr && allowCopyFiles) { idFile* preOpened = preCallback(relativePath); const std::size_t length = relativePath == nullptr ? 0 : std::strlen(relativePath); const bool loadout = length >= 8 && EqualNoCase(relativePath + length - 8, ".loadout"); if (preOpened != nullptr || !loadout) return preOpened; } if (cacheCallback != nullptr && allowCopyFiles) { idFile* cached = cacheCallback(relativePath); if (cached != nullptr) return cached; } else if (cacheStatCallback != nullptr && !allowCopyFiles) { idFile* stat = cacheStatCallback(relativePath); if (stat != nullptr) return stat; } if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (relativePath == nullptr) idLibPrint::FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n"); const char* path = StripRecoveredBasePrefix(relativePath); if (path[0] == '\0') return nullptr; idFile* result = nullptr; idStr foundPath; for (int index = 0; index < searchPaths.Num() && result == nullptr; ++index) { const idStr osPath = BuildOSPath(searchPaths[index].c_str(), path); if (PrefixNoCase(osPath.c_str(), "mtp:")) { result = OpenFromMTP(osPath.c_str(), uncompressedOnly ? FS_READ_NO_BUFFERING : FS_READ); } else if (PrefixNoCase(osPath.c_str(), "nfs:")) { result = OpenFromNFS(osPath.c_str(), FS_READ, false); } else { idFile_Permanent* permanent = new idFile_Permanent(path, osPath.c_str(), uncompressedOnly ? FS_READ_NO_BUFFERING : FS_READ, false); if (permanent->IsOpen()) result = permanent; else delete permanent; } if (result != nullptr) foundPath = osPath; } // Recovered arbitrary-zip lookup remains tied to the pending unzip port. if (result != nullptr && postCallback != nullptr) postCallback(foundPath.c_str(), result); return result; } int idFileSystemLocal::AddUnique(const char* name, idList& list, idHashIndex& hash) const { const int key = hash.GenerateKeyForString(name, false); for (int index = hash.First(key); index >= 0; index = hash.Next(index)) { if (EqualNoCase(list[index].c_str(), name)) return index; } const int index = list.Append(idStr(name)); if (index >= 0) hash.Add(key, index); return index; } void idFileSystemLocal::GetExtensionList(const char* extensions, idList& extensionList) const { const char* cursor = extensions == nullptr ? "" : extensions; for (;;) { const char* separator = std::strchr(cursor, '|'); const std::size_t amount = separator == nullptr ? std::strlen(cursor) : static_cast(separator - cursor); std::string extension(cursor, amount); extensionList.Append(idStr(extension.c_str())); if (separator == nullptr) break; cursor = separator + 1; } } int idFileSystemLocal::GetFileList(const char* relativePath, const idList& extensions, idList& list, idHashIndex& hash, const bool fullRelativePath) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (relativePath == nullptr || extensions.Num() == 0) return 0; for (int pathIndex = 0; pathIndex < searchPaths.Num(); ++pathIndex) { const idStr directory = BuildOSPath(searchPaths[pathIndex].c_str(), relativePath); for (int extensionIndex = 0; extensionIndex < extensions.Num(); ++extensionIndex) { idList found; ListOSFiles(directory.c_str(), extensions[extensionIndex].c_str(), found); for (int fileIndex = 0; fileIndex < found.Num(); ++fileIndex) { if (EqualNoCase(found[fileIndex].c_str(), ".") || EqualNoCase(found[fileIndex].c_str(), "..")) continue; if (fullRelativePath && relativePath[0] != '\0') { std::string full = SlashesToGame(relativePath); if (!full.empty() && full.back() != '/') full.push_back('/'); full += found[fileIndex].c_str(); AddUnique(full.c_str(), list, hash); } else { AddUnique(found[fileIndex].c_str(), list, hash); } } } } return list.Num(); } idFileList* idFileSystemLocal::ListFiles(const char* relativePath, const char* extension, const bool sort, const bool fullRelativePath) { idFileList* result = new idFileList(); result->basePath = relativePath; idList extensions; GetExtensionList(extension, extensions); idHashIndex hash(4096, 4096, TAG_HASHINDEX); GetFileList(relativePath, extensions, result->list, hash, fullRelativePath); if (sort && result->list.Num() > 1) std::sort(result->list.Ptr(), result->list.Ptr() + result->list.Num()); return result; } int idFileSystemLocal::GetFileListTree(const char* relativePath, const idList& extensions, idList& list, idHashIndex& hash) { idList directoryExtension; directoryExtension.Append(idStr("/")); idList directories; idHashIndex directoryHash(1024, 128, TAG_HASHINDEX); GetFileList(relativePath, directoryExtension, directories, directoryHash, true); for (int index = 0; index < directories.Num(); ++index) { const char* directory = directories[index].c_str(); const char* leaf = std::strrchr(directory, '/'); leaf = leaf == nullptr ? directory : leaf + 1; if (leaf[0] != '.' && !EqualNoCase(directory, relativePath)) GetFileListTree(directory, extensions, list, hash); } return GetFileList(relativePath, extensions, list, hash, true); } idFileList* idFileSystemLocal::ListFilesTree(const char* relativePath, const char* extension, const bool sort) { idFileList* result = new idFileList(); result->basePath = relativePath; idList extensions; GetExtensionList(extension, extensions); idHashIndex hash(4096, 4096, TAG_HASHINDEX); GetFileListTree(relativePath, extensions, result->list, hash); if (sort && result->list.Num() > 1) std::sort(result->list.Ptr(), result->list.Ptr() + result->list.Num()); return result; } void idFileSystemLocal::AddGameDirectory(const char* rootPath, const char* gameFolder) { std::string path = SlashesToGame(JoinPath(rootPath, gameFolder)); if (!path.empty() && path.back() != '/') path.push_back('/'); const idStr value(path.c_str()); if (searchPaths.FindIndex(value) < 0) searchPaths.Insert(value, 0); } void idFileSystemLocal::ReInit(const char* basePath) { pathBase = basePath; searchPaths.ClearFree(); AddGameDirectory(pathBase.c_str(), "base"); } void idFileSystemLocal::ClearZipCache() { while (cachedZipFiles != nullptr) { idCachedZipFile* next = cachedZipFiles->next; delete cachedZipFiles; cachedZipFiles = next; } } void idFileSystemLocal::FreeFileList(idFileList* fileList) { delete fileList; } void idFileSystemLocal::Init() { pathBase = EnvironmentPath("TECH5_BASE_PATH").c_str(); pathCache = EnvironmentPath("TECH5_CACHE_PATH").c_str(); pathSave = EnvironmentPath("TECH5_SAVE_PATH").c_str(); pathInstall = EnvironmentPath("TECH5_INSTALL_PATH").c_str(); if (pathBase.Length() == 0) pathBase = ExecutableDirectory().c_str(); if (pathInstall.Length() == 0) pathInstall = pathBase; searchPaths.ClearFree(); AddGameDirectory(pathBase.c_str(), "base"); if (pathSave.Length() > 0) AddGameDirectory(pathSave.c_str(), "base"); if (pathInstall.Length() > 0) AddGameDirectory(pathInstall.c_str(), "base"); } bool idFileSystemLocal::RemoveFile(const char* relativePath, const fsPath_t basePath) { const idStr path = BuildOSPath(basePath, relativePath); if (PrefixNoCase(path.c_str(), "nfs:")) return idFile_Nfs::RemoveFile(path.c_str()); DWORD attributes = GetFileAttributesA(path.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) != 0) { SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); } return DeleteFileA(path.c_str()) != FALSE; } bool idFileSystemLocal::FileExists(const char* relativePath, const bool allowCopyFiles) { return GetTimestamp(relativePath, allowCopyFiles) != static_cast(-1); } unsigned int idFileSystemLocal::GetTimestamp(const char* relativePath, const bool allowCopyFiles) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (relativePath == nullptr || relativePath[0] == '\0') idLibPrint::FatalError( "idFileSystemLocal::GetTimestamp with empty name\n"); idFileLocal file(OpenFileRead(relativePath, allowCopyFiles, false)); return file.file == nullptr ? static_cast(-1) : file->Timestamp(); } bool idFileSystemLocal::PreOpenFileWrite(const char* relativePath, fsPath_t& basePath, idStr& osPath) const { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); if (relativePath == nullptr || relativePath[0] == '\0') return false; if (PrefixNoCase(relativePath, "generated")) basePath = FSPATH_SAVE; osPath = BuildOSPath(basePath, relativePath); return true; } idFile* idFileSystemLocal::OpenFileWritePermanent(const char* relativePath, fsPath_t basePath) { idStr osPath; if (!PreOpenFileWrite(relativePath, basePath, osPath)) return nullptr; if (PrefixNoCase(osPath.c_str(), "mtp:")) return OpenFromMTP(osPath.c_str(), FS_WRITE); if (PrefixNoCase(osPath.c_str(), "nfs:")) return OpenFromNFS(osPath.c_str(), FS_WRITE, true); CreateOSPath(osPath.c_str()); idFile_Permanent* file = new idFile_Permanent(relativePath, osPath.c_str(), FS_WRITE, false); if (file->IsOpen()) return file; delete file; return nullptr; } idFile* idFileSystemLocal::OpenFileReadWrite(const char* relativePath, const bool create, const fsPath_t basePath) { if (!IsInitialized()) idLibPrint::FatalError("Filesystem call made without initialization\n"); const idStr osPath = BuildOSPath(basePath, relativePath); if (PrefixNoCase(osPath.c_str(), "mtp:")) return OpenFromMTP(osPath.c_str(), FS_READ_WRITE); if (PrefixNoCase(osPath.c_str(), "nfs:")) return OpenFromNFS(osPath.c_str(), FS_READ_WRITE, create); CreateOSPath(osPath.c_str()); idFile_Permanent* file = new idFile_Permanent(relativePath, osPath.c_str(), FS_READ_WRITE, create); if (file->IsOpen()) return file; delete file; return nullptr; } idFile* idFileSystemLocal::OpenFileWrite(const char* relativePath, const fsPath_t basePath) { // Atomic buffering is restored with the remaining recovered file.cpp body. return OpenFileWritePermanent(relativePath, basePath); } unsigned int idFileSystemLocal::WriteFile(const char* relativePath, const void* buffer, const unsigned int length, const fsPath_t basePath) { idFileLocal file(OpenFileWrite(relativePath, basePath)); if (file.file == nullptr) idLibPrint::Error("Failed to open %s\n", relativePath); return file->Write(buffer, length); } void idFileSystemLocal::Copy_f(const idCmdArgs& args) { if (args.Argc() == 3) fileSystemLocal.CopyGameFile(args.Argv(1), args.Argv(2), FSPATH_BASE); } void idFileSystemLocal::Dir_f(const idCmdArgs& args) { const char* path = args.Argc() > 1 ? args.Argv(1) : ""; const char* extension = args.Argc() > 2 ? args.Argv(2) : ""; idFileList* files = fileSystemLocal.ListFiles(path, extension, true, false); for (int index = 0; index < files->GetNumFiles(); ++index) idLibPrint::Printf("%s\n", files->GetFile(index)); fileSystemLocal.FreeFileList(files); } void idFileSystemLocal::DirTree_f(const idCmdArgs& args) { const char* path = args.Argc() > 1 ? args.Argv(1) : ""; const char* extension = args.Argc() > 2 ? args.Argv(2) : ""; idFileList* files = fileSystemLocal.ListFilesTree(path, extension, true); for (int index = 0; index < files->GetNumFiles(); ++index) idLibPrint::Printf("%s\n", files->GetFile(index)); fileSystemLocal.FreeFileList(files); } void idFileSystemLocal::Path_f(const idCmdArgs&) { idLibPrint::Printf("Current search path:\n"); for (int index = 0; index < fileSystemLocal.searchPaths.Num(); ++index) idLibPrint::Printf("%s\n", fileSystemLocal.searchPaths[index].c_str()); }