diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e7ef5..d2f2986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * Fixed scientists crashing when speaking fear dialogue when enemy has been removed * Disabled fall think function for weapons when the player picks it up to prevent possible double-pickup which removes the weapon and crashes the game * Disabled jump sounds while player is frozen (e.g. trigger_camera, trigger_playerfreeze) +* Fixed node graph code incorrectly flagging node graphs as out of date if an outdated graph exists in a search path other than the mod directory (e.g. a graph in `halflife_updated_addon/map/graphs`) ## Changes in V1.0.0 Beta 014 diff --git a/dlls/nodes.cpp b/dlls/nodes.cpp index cc68c54..e32d2c0 100644 --- a/dlls/nodes.cpp +++ b/dlls/nodes.cpp @@ -2612,7 +2612,7 @@ bool CGraph::CheckNODFile(const char* szMapName) bool retValue = true; int iCompare; - if (COMPARE_FILE_TIME(bspFileName.c_str(), graphFileName.c_str(), &iCompare)) + if (FileSystem_CompareFileTime(bspFileName.c_str(), graphFileName.c_str(), &iCompare)) { if (iCompare > 0) { // BSP file is newer. diff --git a/game_shared/filesystem_utils.cpp b/game_shared/filesystem_utils.cpp index c21e552..188cce2 100644 --- a/game_shared/filesystem_utils.cpp +++ b/game_shared/filesystem_utils.cpp @@ -13,20 +13,106 @@ * ****/ +#include #include #include +#include #include "Platform.h" +#include "PlatformHeaders.h" + +#ifdef WIN32 +#include +#include +#endif + +#ifdef LINUX +#include +#include +#include +#endif #include "extdll.h" #include "util.h" +#ifdef CLIENT_DLL +#include "hud.h" +#endif + #include "interface.h" #include "filesystem_utils.h" static CSysModule* g_pFileSystemModule = nullptr; +// Some methods used to launch the game don't set the working directory. +// This makes using relative paths that point to the game and/or mod directory difficult +// since C and C++ runtime APIs don't know to use the game directory as a base. +// As a workaround we look up the executable directory and use that as a base. +// See https://stackoverflow.com/a/1024937/1306648 for more platform-specific workarounds. +// The engine's filesystem doesn't provide functions to do this so we have to work around it. +static std::string g_GameDirectory; +static std::string g_ModDirectory; + +static bool FileSystem_InitializeGameDirectory() +{ + std::string gameDirectory; + +#ifdef WIN32 + const std::size_t BufferSize = MAX_PATH + 1; + gameDirectory.resize(BufferSize); + + const DWORD charactersWritten = GetModuleFileNameA(NULL, gameDirectory.data(), BufferSize); + + if (charactersWritten == BufferSize) + { + // Path was truncated. Game is installed in the wrong location (Steam shouldn't allow this). + return false; + } +#else + const std::size_t BufferSize = PATH_MAX + 1; + directory.resize(BufferSize); + + const ssize_t charactersWritten = readlink("/proc/self/exe", directory.data(), BufferSize); + + if (charactersWritten < 0 || charactersWritten == BufferSize) + { + // Path was truncated. Game is installed in the wrong location (Steam shouldn't allow this). + return false; + } +#endif + + // Resize buffer to actual size. + gameDirectory.resize(std::strlen(gameDirectory.c_str())); + + // Truncate to directory name. + const std::size_t directoryEnd = gameDirectory.find_last_of(DefaultPathSeparatorChar); + + if (directoryEnd == std::string::npos) + { + return false; + } + + gameDirectory.resize(directoryEnd); + + gameDirectory.shrink_to_fit(); + + std::string modDirectory; + modDirectory.resize(BufferSize); + +#ifdef CLIENT_DLL + modDirectory = gEngfuncs.pfnGetGameDirectory(); +#else + g_engfuncs.pfnGetGameDir(modDirectory.data()); + modDirectory.resize(std::strlen(modDirectory.c_str())); +#endif + + g_GameDirectory = std::move(gameDirectory); + g_ModDirectory = g_GameDirectory + DefaultPathSeparatorChar + modDirectory; + + return true; +} + bool FileSystem_LoadFileSystem() { if (nullptr != g_pFileSystem) @@ -73,6 +159,11 @@ bool FileSystem_LoadFileSystem() return false; } + if (!FileSystem_InitializeGameDirectory()) + { + return false; + } + return true; } @@ -90,6 +181,75 @@ void FileSystem_FreeFileSystem() } } +void FileSystem_FixSlashes(std::string& fileName) +{ + std::replace(fileName.begin(), fileName.end(), AlternatePathSeparatorChar, DefaultPathSeparatorChar); +} + +time_t FileSystem_GetFileTime(const char* fileName) +{ + if (nullptr == fileName) + { + return 0; + } + + std::string absoluteFileName = g_ModDirectory + DefaultPathSeparatorChar + fileName; + + FileSystem_FixSlashes(absoluteFileName); + +#ifdef WIN32 + struct _stat64i32 buf; + + const int result = _stat64i32(absoluteFileName.c_str(), &buf); + + if (result != 0) + { + return 0; + } + + const time_t value = std::max(buf.st_ctime, buf.st_mtime); + + return value; +#else + struct stat buf; + + const int result = stat(absoluteFileName.c_str(), &buf); + + if (result != 0) + { + return 0; + } + + const time_t value = std::max(buf.st_ctim.tv_sec, buf.st_mtim.tv_sec); + + return value; +#endif +} + +bool FileSystem_CompareFileTime(const char* filename1, const char* filename2, int* iCompare) +{ + *iCompare = 0; + + if (!filename1 || !filename2) + { + return false; + } + + const time_t time1 = FileSystem_GetFileTime(filename1); + const time_t time2 = FileSystem_GetFileTime(filename2); + + if (time1 < time2) + { + *iCompare = -1; + } + else if (time1 > time2) + { + *iCompare = 1; + } + + return true; +} + std::vector FileSystem_LoadFileIntoBuffer(const char* fileName, FileContentFormat format, const char* pathID) { assert(nullptr != g_pFileSystem); diff --git a/game_shared/filesystem_utils.h b/game_shared/filesystem_utils.h index 2da70aa..0877c0d 100644 --- a/game_shared/filesystem_utils.h +++ b/game_shared/filesystem_utils.h @@ -24,16 +24,50 @@ */ #include +#include +#include #include #include "Platform.h" #include "FileSystem.h" +#ifdef WIN32 +constexpr char DefaultPathSeparatorChar = '\\'; +constexpr char AlternatePathSeparatorChar = '/'; +#else +constexpr char DefaultPathSeparatorChar = '/'; +constexpr char AlternatePathSeparatorChar = '\\'; +#endif + inline IFileSystem* g_pFileSystem = nullptr; bool FileSystem_LoadFileSystem(); void FileSystem_FreeFileSystem(); +/** +* @brief Replaces occurrences of ::AlternatePathSeparatorChar with ::DefaultPathSeparatorChar. +*/ +void FileSystem_FixSlashes(std::string& fileName); + +/** +* @brief Returns the last modification time of the given file. +* Filenames are relative to the game directory. +*/ +time_t FileSystem_GetFileTime(const char* fileName); + +/** +* @brief Compares the file time of the given files located in the mod directory. +* @details Needed because IFileSystem::GetFileTime() does not provide a path ID parameter. +* @param filename1 First file to compare. +* @param filename2 Second file to compare. +* @param[out] iCompare Stores the result of the comparison. +* -@c 0 if equal +* -@c -1 if @p filename2 is newer than @p filename1 +* -@c 1 if @p filename1 is newer than @p filename2 +* @return @c true if filetimes were retrieved, false otherwise. +*/ +bool FileSystem_CompareFileTime(const char* filename1, const char* filename2, int* iCompare); + enum class FileContentFormat { Binary = 0,