Files
tech5/source/shared/idlib/filesystem/filesystem.cpp
T
2026-08-08 19:30:03 -07:00

899 lines
34 KiB
C++

#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 <Windows.h>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
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<unsigned char>(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<DWORD>(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<std::size_t>(destinationSize),
_TRUNCATE, "%s", source == nullptr ? "" : source);
}
std::string ExecutableDirectory() {
char path[32768] = {};
const DWORD amount = GetModuleFileNameA(nullptr, path,
static_cast<DWORD>(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<DWORD>(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<unsigned int>(-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<unsigned int>(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<idStr>& 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<unsigned int>(-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<int>(fileLength);
if (timestamp != nullptr) *timestamp = file->Timestamp();
if (buffer != nullptr) {
++loadCount;
++loadStack;
unsigned char* data = static_cast<unsigned char*>(
std::malloc(static_cast<std::size_t>(length) + 1));
if (data == nullptr) {
--loadStack;
return -1;
}
const unsigned int amount = file->Read(data,
static_cast<unsigned int>(length));
if (amount != static_cast<unsigned int>(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<idStr>& 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<idStr>& 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<idStr>& 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<std::size_t>(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<idStr>& extensions, idList<idStr>& 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<idStr> 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<idStr> 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<idStr>& extensions, idList<idStr>& list,
idHashIndex& hash) {
idList<idStr> directoryExtension;
directoryExtension.Append(idStr("/"));
idList<idStr> 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<idStr> 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<unsigned int>(-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<unsigned int>(-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());
}