diff --git a/msvc/ReHLDS.sln b/msvc/ReHLDS.sln
index e5ede79..c8e3daa 100644
--- a/msvc/ReHLDS.sln
+++ b/msvc/ReHLDS.sln
@@ -38,6 +38,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Director", "..\rehlds\HLTV\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Proxy", "..\rehlds\HLTV\Proxy\msvc\Proxy.vcxproj", "{ADDFF069-D39D-4A1B-87C9-85A62B980547}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "filesystem_stdio", "..\rehlds\filesystem\FileSystem_Stdio\msvc\filesystem_stdio.vcxproj", "{A428392F-52FB-489E-87D8-623528C7F4F9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug Play|Win32 = Debug Play|Win32
@@ -251,6 +253,28 @@ Global
{ADDFF069-D39D-4A1B-87C9-85A62B980547}.Test Fixes|Win32.Build.0 = Release|Win32
{ADDFF069-D39D-4A1B-87C9-85A62B980547}.Tests|Win32.ActiveCfg = Release|Win32
{ADDFF069-D39D-4A1B-87C9-85A62B980547}.Tests|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Play|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Play|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Record|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Record|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Swds Play|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Swds Play|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Swds|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug Swds|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Play|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Play|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Swds Play|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Swds Play|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Swds|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release Swds|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Test Fixes|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Test Fixes|Win32.Build.0 = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Tests|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Tests|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/publish.gradle b/publish.gradle
index e46f830..6e94cb5 100644
--- a/publish.gradle
+++ b/publish.gradle
@@ -62,6 +62,10 @@ task publishPrepareFiles {
_copyFile('publish/director.dll', 'publish/publishRoot/bin/win32/valve/dlls/director.dll')
_copyFile('publish/director.so', 'publish/publishRoot/bin/linux32/valve/dlls/director.so')
+ // FileSystem binaries
+ _copyFile('publish/filesystem_stdio.dll', 'publish/publishRoot/bin/win32/filesystem_stdio.dll')
+ _copyFile('publish/filesystem_stdio.so', 'publish/publishRoot/bin/linux32/filesystem_stdio.so')
+
// hlsdk
project.file('publish/publishRoot/hlsdk').mkdirs()
copy {
diff --git a/rehlds/filesystem/FileSystem_Stdio/build.gradle b/rehlds/filesystem/FileSystem_Stdio/build.gradle
new file mode 100644
index 0000000..93910cc
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/build.gradle
@@ -0,0 +1,158 @@
+import org.doomedsociety.gradlecpp.cfg.ToolchainConfigUtils
+import org.doomedsociety.gradlecpp.msvc.MsvcToolchainConfig
+import org.doomedsociety.gradlecpp.toolchain.icc.Icc
+import org.doomedsociety.gradlecpp.toolchain.icc.IccCompilerPlugin
+import org.doomedsociety.gradlecpp.gcc.GccToolchainConfig
+import org.doomedsociety.gradlecpp.GradleCppUtils
+import org.gradle.nativeplatform.NativeBinarySpec
+import org.gradle.nativeplatform.NativeLibrarySpec
+import org.gradle.nativeplatform.toolchain.VisualCpp
+
+apply plugin: 'cpp'
+apply plugin: IccCompilerPlugin
+apply plugin: GccCompilerPlugin
+
+void setupToolchain(NativeBinarySpec b) {
+ boolean useGcc = project.hasProperty("useGcc")
+ def cfg = rootProject.createToolchainConfig(b);
+ cfg.projectInclude(project, '/../..', '/src', '/../../common', '/../../public', '/../../public/rehlds');
+ cfg.singleDefines 'USE_BREAKPAD_HANDLER'
+
+ if (cfg instanceof MsvcToolchainConfig) {
+ cfg.compilerOptions.pchConfig = new MsvcToolchainConfig.PrecompiledHeadersConfig(
+ enabled: true,
+ pchHeader: 'precompiled.h',
+ pchSourceSet: 'filesystem_pch'
+ );
+
+ cfg.singleDefines('_CRT_SECURE_NO_WARNINGS')
+ cfg.compilerOptions.args '/Ob2', '/Oi', '/GF'
+ }
+ else if (cfg instanceof GccToolchainConfig) {
+ if (!useGcc) {
+ cfg.compilerOptions.pchConfig = new GccToolchainConfig.PrecompilerHeaderOptions(
+ enabled: true,
+ pchSourceSet: 'filesystem_pch'
+ );
+ }
+
+ cfg.compilerOptions.languageStandard = 'c++11'
+ cfg.defines([
+ '_strdup': 'strdup',
+ '_stricmp': 'strcasecmp',
+ '_strnicmp': 'strncasecmp',
+ '_vsnprintf': 'vsnprintf',
+ '_snprintf': 'snprintf',
+ '_unlink': 'unlink',
+ ]);
+
+ if (useGcc) {
+ // Produce code optimized for the most common IA32/AMD64/EM64T processors.
+ // As new processors are deployed in the marketplace, the behavior of this option will change.
+ cfg.compilerOptions.args '-mtune=generic', '-Wno-write-strings', '-msse3', '-flto'
+ } else {
+ cfg.compilerOptions.args '-Qoption,cpp,--treat_func_as_string_literal_cpp'
+ }
+
+ cfg.compilerOptions.args '-fno-exceptions'
+ cfg.compilerOptions.interProceduralOptimizations = false;
+ cfg.linkerOptions.interProceduralOptimizations = false;
+
+ def funcToWrap = [
+ "freopen", "fopen", "fopen64", "open", "open64", "creat", "access",
+ "stat", "lstat", "scandir", "opendir", "__xstat", "__lxstat", "__xstat64", "__lxstat64",
+ "chmod", "chown", "lchown", "symlink", "link", "mknod", "mount", "unlink",
+ "mkfifo", "rename", "utime", "utimes", "mkdir", "rmdir"
+ ];
+
+ funcToWrap.each {
+ cfg.linkerOptions.args "-Wl,-wrap," + it
+ }
+ }
+
+ ToolchainConfigUtils.apply(project, cfg, b);
+}
+
+model {
+ buildTypes {
+ release
+ }
+
+ platforms {
+ x86 {
+ architecture "x86"
+ }
+ }
+
+ toolChains {
+ visualCpp(VisualCpp) {
+ }
+ if (project.hasProperty("useGcc")) {
+ gcc(Gcc)
+ } else {
+ icc(Icc)
+ }
+ }
+
+ components {
+ filesystem(NativeLibrarySpec) {
+ targetPlatform 'x86'
+ baseName 'filesystem_stdio'
+
+ sources {
+ filesystem_main(CppSourceSet) {
+ source {
+ srcDir "src"
+ include "**/*.cpp"
+ exclude "precompiled.cpp"
+ }
+ }
+
+ filesystem_pch(CppSourceSet) {
+ source {
+ srcDir "src"
+ include "precompiled.cpp"
+ }
+ }
+ }
+
+ binaries.all {
+ NativeBinarySpec b -> project.setupToolchain(b)
+ }
+ }
+ }
+}
+
+task buildFinalize << {
+ if (GradleCppUtils.windows) {
+ return;
+ }
+
+ binaries.withType(SharedLibraryBinarySpec) {
+ def sharedBinary = it.getSharedLibraryFile();
+ if (sharedBinary.exists()) {
+ sharedBinary.renameTo(new File(sharedBinary.getParent() + "/" + sharedBinary.getName().replaceFirst("^lib", "")));
+ }
+ }
+}
+
+task buildFixes {
+ dependsOn binaries.withType(SharedLibraryBinarySpec).matching { SharedLibraryBinarySpec blib ->
+ blib.buildable && blib.buildType.name == 'release'
+ }
+}
+
+task buildRelease {
+ dependsOn binaries.withType(SharedLibraryBinarySpec).matching { SharedLibraryBinarySpec blib ->
+ blib.buildable && blib.buildType.name == 'release'
+ }
+}
+
+build.finalizedBy(buildFinalize);
+buildFixes.finalizedBy(buildFinalize);
+buildRelease.finalizedBy(buildFinalize);
+
+// prevent static lib building
+binaries.withType(StaticLibraryBinarySpec) { binary ->
+ buildable = false
+}
diff --git a/rehlds/filesystem/FileSystem_Stdio/msvc/PostBuild.bat b/rehlds/filesystem/FileSystem_Stdio/msvc/PostBuild.bat
new file mode 100644
index 0000000..8583878
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/msvc/PostBuild.bat
@@ -0,0 +1,39 @@
+@echo OFF
+::
+:: Post-build auto-deploy script
+:: Create and fill PublishPath.txt file with path to deployment folder
+:: I.e. PublishPath.txt should contain one line with a folder path
+:: Call it so:
+:: IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)")
+::
+
+SET targetDir=%~1
+SET targetName=%~2
+SET targetExt=%~3
+SET projectDir=%~4
+SET destination=
+
+IF NOT EXIST "%projectDir%\PublishPath.txt" (
+ ECHO No deployment path specified. Create PublishPath.txt near PostBuild.bat with paths on separate lines for auto deployment.
+ exit /B 0
+)
+
+FOR /f "tokens=* delims= usebackq" %%a IN ("%projectDir%\PublishPath.txt") DO (
+ ECHO Deploying to: %%a
+ IF NOT "%%a" == "" (
+ copy /Y "%targetDir%%targetName%%targetExt%" "%%a"
+ IF NOT ERRORLEVEL 1 (
+ IF EXIST "%targetDir%%targetName%.pdb" (
+ copy /Y "%targetDir%%targetName%.pdb" "%%a"
+ )
+ ) ELSE (
+ ECHO PostBuild.bat ^(27^) : warning : Can't copy '%targetName%%targetExt%' to deploy path '%%a'
+ )
+ )
+)
+
+IF "%%a" == "" (
+ ECHO No deployment path specified.
+)
+
+exit /B 0
\ No newline at end of file
diff --git a/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.sln b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.sln
new file mode 100644
index 0000000..8652651
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "filesystem_stdio", "filesystem_stdio.vcxproj", "{A428392F-52FB-489E-87D8-623528C7F4F9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Debug|Win32.Build.0 = Debug|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release|Win32.ActiveCfg = Release|Win32
+ {A428392F-52FB-489E-87D8-623528C7F4F9}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj
new file mode 100644
index 0000000..fbf39c2
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj
@@ -0,0 +1,174 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {A428392F-52FB-489E-87D8-623528C7F4F9}
+ Win32Proj
+ filesystem_stdio
+ 8.1
+
+
+
+ DynamicLibrary
+ true
+ v120_xp
+ v140_xp
+ v141_xp
+ MultiByte
+
+
+ DynamicLibrary
+ false
+ v120_xp
+ v140_xp
+ v141_xp
+ true
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+
+ Use
+ Level3
+ Disabled
+ WIN32;_WINDOWS;USE_BREAKPAD_HANDLER;_CRT_SECURE_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions)
+ precompiled.h
+ $(ProjectDir)..\src;$(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common;$(ProjectDir)..\..\..\public;$(ProjectDir)..\..\..\public\rehlds;%(AdditionalIncludeDirectories)
+ MultiThreadedDebug
+ EnableFastChecks
+ false
+ EditAndContinue
+ Default
+ false
+
+
+
+
+ Windows
+ true
+ psapi.lib;%(AdditionalDependencies)
+
+
+ IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)")
+ Automatic deployment script
+
+
+ echo Empty Action
+
+
+ Force build to run Pre-Build event
+
+
+ build.always.run
+
+
+ build.always.run
+
+
+
+
+ Level3
+ Use
+ MinSpace
+ true
+ false
+ WIN32;_WINDOWS;USE_BREAKPAD_HANDLER;_CRT_SECURE_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions)
+ precompiled.h
+ $(ProjectDir)..\src;$(ProjectDir)..\..\..\;$(ProjectDir)..\..\..\common;$(ProjectDir)..\..\..\public;$(ProjectDir)..\..\..\public\rehlds;%(AdditionalIncludeDirectories)
+ MultiThreaded
+ Disabled
+
+
+ Windows
+ true
+ true
+ true
+ psapi.lib;%(AdditionalDependencies)
+
+
+ IF EXIST "$(ProjectDir)PostBuild.bat" (CALL "$(ProjectDir)PostBuild.bat" "$(TargetDir)" "$(TargetName)" "$(TargetExt)" "$(ProjectDir)")
+ Automatic deployment script
+
+
+ echo Empty Action
+
+
+ Force build to run Pre-Build event
+
+
+ build.always.run
+
+
+ build.always.run
+
+
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+ Create
+ Create
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj.filters b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj.filters
new file mode 100644
index 0000000..5f4abdf
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/msvc/filesystem_stdio.vcxproj.filters
@@ -0,0 +1,60 @@
+
+
+
+
+ {3f7611e3-cf43-4956-a8a1-b6efbcf1a63d}
+
+
+ {cacc7b07-9ac2-42d5-bba3-14c061a19d11}
+
+
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ hookers
+
+
+ hookers
+
+
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ src
+
+
+ hookers
+
+
+ src
+
+
+
\ No newline at end of file
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.cpp b/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.cpp
new file mode 100644
index 0000000..cc94e93
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.cpp
@@ -0,0 +1,1676 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#include "precompiled.h"
+
+float g_flDummyFloat = 1.0f;
+CBaseFileSystem *CBaseFileSystem::s_pFileSystem;
+
+CBaseFileSystem *BaseFileSystem()
+{
+ return CBaseFileSystem::s_pFileSystem;
+}
+
+CBaseFileSystem::CBaseFileSystem()
+{
+ s_pFileSystem = this;
+
+ m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED;
+ m_pfnWarning = nullptr;
+ m_nOpenCount = 0;
+}
+
+void CBaseFileSystem::Mount()
+{
+ g_flDummyFloat += g_flDummyFloat;
+}
+
+void CBaseFileSystem::Unmount()
+{
+ ;
+}
+
+void CBaseFileSystem::CreateDirHierarchy(const char *path, const char *pathID)
+{
+ CSearchPath *searchPath = GetWritePath(pathID);
+ int len = Q_strlen(searchPath->GetPath().String()) + Q_strlen(path);
+ char *buf = (char *)alloca((len + 1) * sizeof(char));
+
+ Q_strcpy(buf, searchPath->GetPath().String());
+ Q_strcat(buf, path);
+
+ FixSlashes(buf);
+
+ char *s = buf;
+ char *end = &buf[len];
+ while (s < end)
+ {
+ if (*s == CORRECT_PATH_SEPARATOR) {
+ *s = '\0';
+#ifdef _WIN32
+ _mkdir(buf);
+#else
+ mkdir(buf, S_IRWXU | S_IRGRP | S_IROTH); // owner has rwx, rest have r
+#endif // _WIN32
+
+ *s = CORRECT_PATH_SEPARATOR;
+ }
+
+ s++;
+ }
+
+#ifdef _WIN32
+ _mkdir(buf);
+#else
+ mkdir(buf, S_IRWXU | S_IRGRP | S_IROTH); // owner has rwx, rest have r
+#endif // _WIN32
+}
+
+void CBaseFileSystem::PrintOpenedFiles()
+{
+ Trace_DumpUnclosedFiles();
+}
+
+bool CBaseFileSystem::GetCurrentDirectory(char *pDirectory, int maxlen)
+{
+#ifdef _WIN32
+ if (!::GetCurrentDirectoryA(maxlen, pDirectory))
+#else
+ if (!getcwd(pDirectory, maxlen))
+#endif // _WIN32
+ return false;
+
+ FixSlashes(pDirectory);
+
+ // Strip the last slash
+ int len = Q_strlen(pDirectory);
+ if (pDirectory[len - 1] == CORRECT_PATH_SEPARATOR) {
+ pDirectory[len - 1] = '\0';
+ }
+
+ return true;
+}
+
+void CBaseFileSystem::AddSearchPathNoWrite(const char *pPath, const char *pathID)
+{
+ AddSearchPathInternal(pPath, pathID, false);
+}
+
+// Create the search path
+void CBaseFileSystem::AddSearchPath(const char *pPath, const char *pathID)
+{
+ AddSearchPathInternal(pPath, pathID, true);
+}
+
+// This is where search paths are created. Map files are created at head of list
+// (they occur after file system paths have already been set) so they get highest priority.
+// Otherwise, we add the disk (non-packfile) path and then the paks if they exist for the path.
+void CBaseFileSystem::AddSearchPathInternal(const char *pPath, const char *pathID, bool bAllowWrite)
+{
+ if (Q_strstr(pPath, ".bsp")) {
+ return;
+ }
+
+ // Clean up the name
+ char *newPath = (char *)alloca((MAX_PATH + 1) * sizeof(char));
+
+#ifdef _WIN32
+ if (Q_strchr(pPath, ':'))
+#else
+ if (pPath && *pPath == '/')
+#endif // _WIN32
+ {
+ Q_strcpy(newPath, pPath);
+ }
+ else
+ {
+ GetCurrentDirectory(newPath, MAX_PATH);
+ FixPath(newPath);
+
+ if (Q_strcmp(pPath, ".") != 0) {
+ Q_strcat(newPath, pPath);
+ }
+ }
+
+#ifdef _WIN32 // don't do this on linux!
+ Q_strlwr(newPath);
+#endif // _WIN32
+
+ FixPath(newPath);
+
+ // Make sure that it doesn't already exist
+ CUtlSymbol pathSymbol(newPath);
+ CUtlSymbol pathIDSymbol(pathID);
+
+ int c = m_SearchPaths.Count();
+ for (int i = 0; i < c; i++)
+ {
+ CSearchPath *pSearchPath = &m_SearchPaths[i];
+ if (pSearchPath->GetPath() == pathSymbol && pSearchPath->GetPathID() == pathIDSymbol) {
+ // this entry is already at the head
+ return;
+ }
+ }
+
+ // Add to list
+ int newIndex = m_SearchPaths.AddToTail();
+
+ CSearchPath *sp = &m_SearchPaths[newIndex];
+ sp->SetPath(pathSymbol);
+ sp->SetPathID(pathIDSymbol);
+ sp->SetWritePath(bAllowWrite);
+
+ // Add pack files for this path next
+ AddPackFiles(newPath);
+}
+
+// Returns true on success, false on failure
+bool CBaseFileSystem::RemoveSearchPath(const char *pPath)
+{
+ char *newPath = (char *)alloca((Q_strlen(pPath) + 1) * sizeof(char));
+
+#ifdef _WIN32
+ if (Q_strchr(pPath, ':'))
+#else
+ if (pPath && *pPath == '/')
+#endif // _WIN32
+ {
+ Q_strcpy(newPath, pPath);
+ }
+ else
+ {
+ GetCurrentDirectory(newPath, MAX_PATH);
+ FixPath(newPath);
+ if (Q_strcmp(pPath, ".")) {
+ Q_strcat(newPath, pPath);
+ }
+ }
+
+#ifdef _WIN32 // don't do this on linux!
+ Q_strlwr(newPath);
+#endif // _WIN32
+
+ FixPath(newPath);
+
+ CUtlSymbol lookup(newPath);
+
+ bool bret = false;
+
+ // Count backward since we're possibly deleting one or more pack files, too
+ int i;
+ int c = m_SearchPaths.Count();
+ for (i = c - 1; i >= 0; i--)
+ {
+ if (m_SearchPaths[i].GetPath() != lookup)
+ continue;
+
+ m_SearchPaths.Remove(i);
+ bret = true;
+ }
+
+ return bret;
+}
+
+// Finds a search path that should be used for writing to, given a pathID
+CBaseFileSystem::CSearchPath *CBaseFileSystem::GetWritePath(const char *pathID)
+{
+ int iPath = 0;
+ CSearchPath *searchPath = m_SearchPaths.Base();
+ while (searchPath && iPath < m_SearchPaths.Count())
+ {
+ if (searchPath->IsAllowWrite()) {
+ break;
+ }
+
+ searchPath = &m_SearchPaths[iPath++];
+ }
+
+ if (!pathID || m_SearchPaths.Count() <= 0) {
+ return searchPath;
+ }
+
+ CUtlSymbol lookup(pathID);
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ if (m_SearchPaths[i].GetPathID() == lookup) {
+ return &m_SearchPaths[i];
+ }
+ }
+
+ return searchPath;
+}
+
+// Returns true on success, false on failure
+bool CBaseFileSystem::IsDirectory(const char *pFileName)
+{
+ // Allow for UNC-type syntax to specify the path ID.
+ struct _stat buf;
+
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ CSearchPath *sp = &m_SearchPaths[i];
+ if (!sp->IsPackFile())
+ {
+ int len = Q_strlen(sp->GetPath().String()) + Q_strlen(pFileName);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, sp->GetPath().String());
+ Q_strcat(pTmpFileName, pFileName);
+
+ FixSlashes(pTmpFileName);
+
+ if (FS_stat(pTmpFileName, &buf) != -1)
+ {
+ if (buf.st_mode & _S_IFDIR)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// The base file search goes through here
+FileHandle_t CBaseFileSystem::FindFile(CSearchPath *path, const char *pFileName, const char *pOptions, bool bFromCache)
+{
+ CFileHandle *fh = nullptr;
+ if (path->IsPackFile())
+ {
+ char *temp = (char *)alloca((Q_strlen(pFileName) + 1) * sizeof(char));
+
+ Q_strcpy(temp, pFileName);
+ Q_strlwr(temp);
+ FixSlashes(temp);
+
+ CPackFileEntry search;
+ search.m_Name = temp;
+ search.m_nPosition = 0;
+ search.m_nLength = 0;
+
+ int searchresult = path->m_PackFiles.Find(search);
+ if (searchresult == path->m_PackFiles.InvalidIndex()) {
+ return FILESYSTEM_INVALID_HANDLE;
+ }
+
+ CPackFileEntry result = path->m_PackFiles[searchresult];
+ FS_fseek(path->m_hPackFile->m_pFile, path->m_hPackFile->m_nStartOffset + result.m_nPosition, FILESYSTEM_SEEK_HEAD);
+
+ fh = new CFileHandle;
+ fh->m_pFile = path->m_hPackFile->m_pFile;
+ fh->m_nStartOffset = result.m_nPosition;
+ fh->m_nLength = result.m_nLength;
+ fh->m_nFileTime = path->m_lPackFileTime;
+ fh->m_bPack = true;
+ fh->m_bErrorFlagged = false;
+ }
+ else if ((!Q_strchr(pOptions, 'w') && !Q_strchr(pOptions, '+')) || path->IsAllowWrite())
+ {
+ int len = Q_strlen(path->GetPath().String()) + Q_strlen(pFileName);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, path->GetPath().String());
+ Q_strcat(pTmpFileName, pFileName);
+
+ FixSlashes(pTmpFileName);
+
+ FILE *fp = Trace_FOpen(pTmpFileName, pOptions, bFromCache);
+ if (!fp) {
+ return FILESYSTEM_INVALID_HANDLE;
+ }
+
+ struct _stat buf;
+ if (FS_stat(pTmpFileName, &buf) == -1) {
+ Warning(FILESYSTEM_WARNING, "_stat on file %s which appeared to exist failed!!!\n", pTmpFileName);
+ }
+
+ fh = new CFileHandle;
+ fh->m_pFile = fp;
+ fh->m_nStartOffset = 0;
+ fh->m_nLength = buf.st_size;
+ fh->m_nFileTime = Q_max(buf.st_ctime, buf.st_mtime);
+ fh->m_bPack = false;
+ fh->m_bErrorFlagged = false;
+ }
+
+ return reinterpret_cast(fh);
+}
+
+// Returns true on success, false on failure.
+bool CBaseFileSystem::FileExists(const char *pFileName)
+{
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ int size = FastFindFileSize(&m_SearchPaths[i], pFileName);
+ if (size != -1) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+FileHandle_t CBaseFileSystem::Open(const char *pFileName, const char *pOptions, const char *pathID)
+{
+ // Try each of the search paths in succession
+ // FIXME: call createdirhierarchy upon opening for write.
+ if (Q_strstr(pOptions, "r") && !Q_strstr(pOptions, "+"))
+ {
+ CUtlSymbol lookup(pathID);
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ if (pathID && m_SearchPaths[i].GetPathID() != lookup) {
+ continue;
+ }
+
+ FileHandle_t filehandle = FindFile(&m_SearchPaths[i], pFileName, pOptions);
+ if (filehandle != FILESYSTEM_INVALID_HANDLE) {
+ return filehandle;
+ }
+ }
+
+ return FILESYSTEM_INVALID_HANDLE;
+ }
+
+ CSearchPath *searchPath = GetWritePath(pathID);
+ int len = Q_strlen(searchPath->GetPath().String()) + Q_strlen(pFileName);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+
+ Q_strcpy(pTmpFileName, searchPath->GetPath().String());
+ Q_strcat(pTmpFileName, pFileName);
+
+ FixSlashes(pTmpFileName);
+
+ FILE *fp = Trace_FOpen(pTmpFileName, pOptions);
+ if (!fp) {
+ return FILESYSTEM_INVALID_HANDLE;
+ }
+
+ struct _stat buf;
+ if (FS_stat(pTmpFileName, &buf) == -1) {
+ Warning(FILESYSTEM_WARNING, "_stat on file %s which appeared to exist failed!!!\n", pTmpFileName);
+ }
+
+ CFileHandle *fh;
+
+ fh = new CFileHandle;
+ fh->m_pFile = fp;
+ fh->m_nStartOffset = 0;
+ fh->m_nLength = buf.st_size;
+ fh->m_nFileTime = buf.st_mtime;
+ fh->m_bPack = false;
+ fh->m_bErrorFlagged = false;
+
+ return reinterpret_cast(fh);
+}
+
+FileHandle_t CBaseFileSystem::OpenFromCacheForRead(const char *pFileName, const char *pOptions, const char *pathID)
+{
+ CUtlSymbol lookup(pathID);
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ if (pathID && m_SearchPaths[i].GetPathID() != lookup) {
+ continue;
+ }
+
+ FileHandle_t filehandle = FindFile(&m_SearchPaths[i], pFileName, pOptions, true);
+ if (filehandle != FILESYSTEM_INVALID_HANDLE) {
+ return filehandle;
+ }
+ }
+
+ return FILESYSTEM_INVALID_HANDLE;
+}
+
+void CBaseFileSystem::Close(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Close NULL file handle!\n");
+ return;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Close NULL file pointer inside valid file handle!\n");
+ return;
+ }
+
+ // found handle in packfiles, we shouldn't release handle
+ if (m_PackFileHandles.Find(fh->m_pFile) != m_PackFileHandles.InvalidIndex()) {
+ return;
+ }
+
+ Trace_FClose(fh->m_pFile);
+ fh->m_pFile = nullptr;
+ delete fh;
+}
+
+void CBaseFileSystem::Seek(FileHandle_t file, int pos, FileSystemSeek_t whence)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "Tried to Seek NULL file handle!\n");
+ return;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Seek NULL file pointer inside valid file handle!\n");
+ return;
+ }
+
+ // check bounds
+ FileSystemSeek_t seekType = whence;
+ if (seekType < FILESYSTEM_SEEK_HEAD || seekType > FILESYSTEM_SEEK_TAIL)
+ seekType = FILESYSTEM_SEEK_HEAD;
+
+ if (!fh->m_bPack)
+ {
+ FS_fseek(fh->m_pFile, pos, seekType);
+ return;
+ }
+
+ if (whence == FILESYSTEM_SEEK_CURRENT)
+ {
+ FS_fseek(fh->m_pFile, pos, seekType);
+ return;
+ }
+
+ if (whence == FILESYSTEM_SEEK_HEAD)
+ {
+ FS_fseek(fh->m_pFile, pos + fh->m_nStartOffset, seekType);
+ return;
+ }
+
+ FS_fseek(fh->m_pFile, pos + fh->m_nStartOffset + fh->m_nLength, seekType);
+}
+
+size_t CBaseFileSystem::Tell(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Tell NULL file handle!\n");
+ return 0;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Tell NULL file pointer inside valid file handle!\n");
+ return 0;
+ }
+
+ // Pack files are relative
+ return FS_ftell(fh->m_pFile) - (size_t)fh->m_nStartOffset;
+}
+
+size_t CBaseFileSystem::Size(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Size NULL file handle!\n");
+ return 0;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Size NULL file pointer inside valid file handle!\n");
+ return 0;
+ }
+
+ return fh->m_nLength;
+}
+
+size_t CBaseFileSystem::Size(const char *pFileName)
+{
+ int i = 0;
+ while (i < m_SearchPaths.Count())
+ {
+ int size = FastFindFileSize(&m_SearchPaths[i], pFileName);
+ if (size != -1) {
+ return size;
+ }
+
+ i++;
+ }
+
+ return -1;
+}
+
+int CBaseFileSystem::FastFindFileSize(CSearchPath *path, const char *pFileName)
+{
+ if (path->IsPackFile())
+ {
+ char *temp = (char *)alloca((Q_strlen(pFileName) + 1) * sizeof(char));
+
+ Q_strcpy(temp, pFileName);
+ Q_strlwr(temp);
+ FixSlashes(temp);
+
+ CPackFileEntry search;
+ search.m_Name = temp;
+ search.m_nPosition = 0;
+ search.m_nLength = 0;
+
+ int searchresult = path->m_PackFiles.Find(search);
+ if (searchresult == path->m_PackFiles.InvalidIndex()) {
+ return -1;
+ }
+
+ return path->m_PackFiles[searchresult].m_nLength;
+ }
+
+ // Is it an absolute path?
+ char pTmpFileName[MAX_PATH];
+ Q_strlcpy(pTmpFileName, path->GetPath().String());
+ Q_strlcat(pTmpFileName, pFileName);
+ FixSlashes(pTmpFileName);
+
+ struct _stat buf;
+ if (FS_stat(pTmpFileName, &buf) == -1) {
+ return -1;
+ }
+
+ return buf.st_size;
+}
+
+bool CBaseFileSystem::EndOfFile(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to EndOfFile NULL file handle!\n");
+ return true;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to EndOfFile NULL file pointer inside valid file handle!\n");
+ return true;
+ }
+
+ if (fh->m_bPack)
+ {
+ if (FS_ftell(fh->m_pFile) >= (fh->m_nStartOffset + fh->m_nLength)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ if (FS_feof(fh->m_pFile)) {
+ return true;
+ }
+
+ return false;
+}
+
+int CBaseFileSystem::Read(void *pOutput, int size, FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Read NULL file handle!\n");
+ return 0;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Read NULL file pointer inside valid file handle!\n");
+ return 0;
+ }
+
+ int result = FS_fread(pOutput, 1, size, fh->m_pFile);
+ if (result != size) {
+ fh->m_bErrorFlagged = true;
+ }
+
+ return result;
+}
+
+int CBaseFileSystem::Write(const void *pInput, int size, FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Write NULL file handle!\n");
+ return 0;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Write NULL file pointer inside valid file handle!\n");
+ return 0;
+ }
+
+ return FS_fwrite(pInput, 1, size, fh->m_pFile);
+}
+
+int CBaseFileSystem::FPrintf(FileHandle_t file, const char *fmt, ...)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to FS: fprintf NULL file handle!\n");
+ return 0;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to FS: fprintf NULL file pointer inside valid file handle!\n");
+ return 0;
+ }
+
+ va_list argptr;
+ va_start(argptr, fmt);
+ int len = FS_vfprintf(fh->m_pFile, fmt, argptr);
+ va_end(argptr);
+
+ return len;
+}
+
+void *CBaseFileSystem::GetReadBuffer(FileHandle_t file, int *outBufferSize, bool failIfNotInCache)
+{
+ *outBufferSize = 0;
+ return nullptr;
+}
+
+void CBaseFileSystem::ReleaseReadBuffer(FileHandle_t file, void *readBuffer)
+{
+ ;
+}
+
+bool CBaseFileSystem::IsOk(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file handle!\n");
+ return false;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file pointer inside valid file handle!\n");
+ return false;
+ }
+
+ if (fh->m_bErrorFlagged) {
+ return false;
+ }
+
+ return FS_ferror(fh->m_pFile) == 0 ? true : false;
+}
+
+void CBaseFileSystem::Flush(FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Flush NULL file handle!\n");
+ return;
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to Flush NULL file pointer inside valid file handle!\n");
+ return;
+ }
+
+ FS_fflush(fh->m_pFile);
+}
+
+char *CBaseFileSystem::ReadLine(char *pOutput, int maxChars, FileHandle_t file)
+{
+ CFileHandle *fh = reinterpret_cast(file);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to ReadLine NULL file handle!\n");
+ return "";
+ }
+
+ if (!fh->m_pFile) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to ReadLine NULL file pointer inside valid file handle!\n");
+ return "";
+ }
+
+ return FS_fgets(pOutput, maxChars, fh->m_pFile);
+}
+
+// Deletes a file
+void CBaseFileSystem::RemoveFile(const char *pRelativePath, const char *pathID)
+{
+ CSearchPath *searchPath = GetWritePath(pathID);
+ int len = Q_strlen(searchPath->GetPath().String()) + Q_strlen(pRelativePath);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+
+ Q_strcpy(pTmpFileName, searchPath->GetPath().String());
+ Q_strcat(pTmpFileName, pRelativePath);
+
+ FixSlashes(pTmpFileName);
+ _unlink(pTmpFileName);
+}
+
+// Converts a partial path into a full path
+// Relative paths that are pack based are returned as an absolute path .../zip?.zip/foo
+// A pack absolute path can be sent back in for opening, and the file will be properly
+// detected as pack based and mounted inside the pack.
+const char *CBaseFileSystem::GetLocalPath(const char *pFileName, char *pLocalPath, int localPathBufferSize)
+{
+#ifdef _WIN32
+ if (Q_strchr(pFileName, ':'))
+#else
+ if (pFileName && *pFileName == '/')
+#endif // _WIN32
+ {
+ Q_strncpy(pLocalPath, pFileName, localPathBufferSize);
+ pLocalPath[localPathBufferSize - 1] = '\0';
+ FixSlashes(pLocalPath);
+ return pLocalPath;
+ }
+
+ struct _stat buf;
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ CSearchPath *pSearchPath = &m_SearchPaths[i];
+ if (pSearchPath->IsPackFile())
+ {
+ char *temp = (char *)alloca((Q_strlen(pFileName) + 1) * sizeof(char));
+
+ Q_strcpy(temp, pFileName);
+ Q_strlwr(temp);
+ FixSlashes(temp);
+
+ CPackFileEntry search;
+ search.m_Name = temp;
+ search.m_nPosition = 0;
+ search.m_nLength = 0;
+
+ int searchresult = pSearchPath->m_PackFiles.Find(search);
+ if (searchresult != pSearchPath->m_PackFiles.InvalidIndex())
+ {
+ int len = Q_strlen(pFileName);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, pFileName);
+
+ Q_snprintf(pLocalPath, localPathBufferSize - 1, "%s%s", pSearchPath->GetPath().String(), pTmpFileName);
+ pLocalPath[localPathBufferSize - 1] = '\0';
+ FixSlashes(pLocalPath);
+ return pLocalPath;
+ }
+ }
+ else
+ {
+ int len = Q_strlen(pSearchPath->GetPath().String()) + Q_strlen(pFileName);
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, pSearchPath->GetPath().String());
+ Q_strcat(pTmpFileName, pFileName);
+ FixSlashes(pTmpFileName);
+
+ if (FS_stat(pTmpFileName, &buf) != -1) {
+ Q_strncpy(pLocalPath, pTmpFileName, localPathBufferSize);
+ pLocalPath[localPathBufferSize - 1] = '\0';
+ return pLocalPath;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+FILE *CBaseFileSystem::Trace_FOpen(const char *filename, const char *options, bool bFromCache)
+{
+ FILE *fp = FS_fopen(filename, options, bFromCache);
+ if (!fp) {
+ return nullptr;
+ }
+
+ COpenedFile file;
+ file.SetName(filename);
+ file.m_pFile = fp;
+
+ m_OpenedFiles.AddToTail(file);
+ m_nOpenCount++;
+
+ if (m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES) {
+ Warning(FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS: open %s %p %i\n", filename, fp, m_nOpenCount);
+ }
+
+ return fp;
+}
+
+void CBaseFileSystem::Trace_FClose(FILE *fp)
+{
+ if (!fp) {
+ return;
+ }
+
+ COpenedFile file;
+ file.m_pFile = fp;
+
+ int result = m_OpenedFiles.Find(file);
+ if (result != m_OpenedFiles.InvalidIndex())
+ {
+ m_nOpenCount--;
+
+ COpenedFile found = m_OpenedFiles[result];
+ if (m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES) {
+ Warning(FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS: close %s %p %i\n", found.GetName(), fp, m_nOpenCount);
+ }
+
+ m_OpenedFiles.FindAndRemove(found);
+ }
+ else
+ {
+ Assert(0);
+
+ if (m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES)
+ {
+ Warning(FILESYSTEM_WARNING_REPORTALLACCESSES, "Tried to close unknown file pointer %p\n", fp);
+ }
+ }
+
+ FS_fclose(fp);
+}
+
+void CBaseFileSystem::Trace_DumpUnclosedFiles()
+{
+ for (int i = 0; i < m_OpenedFiles.Count(); i++)
+ {
+ if (m_fwLevel <= FILESYSTEM_WARNING_QUIET) {
+ continue;
+ }
+
+ COpenedFile *found = &m_OpenedFiles[i];
+ Warning(FILESYSTEM_WARNING_REPORTUNCLOSED, "File %s was never closed\n", found ? found->GetName() : "???");
+ }
+}
+
+bool CBaseFileSystem::AddPackFile(const char *pFileName, const char *pathID)
+{
+ return AddPackFileFromPath("", pFileName, true, pathID);
+}
+
+// Adds a pack file from the specified path
+// Returns true on success, false on failure
+bool CBaseFileSystem::AddPackFileFromPath(const char *pPath, const char *pakfile, bool bCheckForAppendedPack, const char *pathID)
+{
+ char fullpath[MAX_PATH];
+ Q_snprintf(fullpath, sizeof(fullpath), "%s%s", pPath, pakfile);
+ FixSlashes(fullpath);
+
+ struct _stat buf;
+ if (FS_stat(fullpath, &buf) == -1) {
+ return false;
+ }
+
+ CFileHandle *fh = new CFileHandle;
+ fh->m_nStartOffset = 0;
+ fh->m_nLength = 0;
+ fh->m_nFileTime = 0;
+ fh->m_bPack = false;
+ fh->m_bErrorFlagged = false;
+ fh->m_pFile = Trace_FOpen(fullpath, "rb");
+
+ // Add this pack file to the search path
+ int newIndex = m_SearchPaths.AddToTail();
+ CSearchPath *sp = &m_SearchPaths[newIndex];
+ sp->m_hPackFile = fh;
+ sp->m_Path = pPath;
+ sp->m_PathID = pathID;
+ sp->m_bIsPackFile = true;
+ sp->m_lPackFileTime = GetFileTime(pakfile);
+
+ int64_t headeroffset = 0LL;
+
+ // Get the length of the pack file
+ if (bCheckForAppendedPack)
+ {
+ // Get the length of the pack file:
+ packappenededheader_t appended;
+ FS_fseek(fh->m_pFile, -signed(sizeof(appended)), FILESYSTEM_SEEK_TAIL);
+ FS_fread(&appended, 1, sizeof(appended), fh->m_pFile);
+
+ if (Q_strcmp(appended.id, "PACKAPPE") != 0)
+ {
+ m_SearchPaths.Remove(newIndex);
+ return false;
+ }
+
+ headeroffset = appended.packheaderpos;
+ }
+
+ if (!PreparePackFile(*sp, headeroffset))
+ {
+ // Failed for some reason, ignore it
+ Trace_FClose(fh->m_pFile);
+ m_SearchPaths.Remove(newIndex);
+ return false;
+ }
+
+ // Add handle to list
+ m_PackFileHandles.AddToTail(fh->m_pFile);
+ return true;
+}
+
+const size_t MAX_DIR_PACK_FILES = 0x8000u;
+
+// Parse the pack file to build the file directory and preload section
+bool CBaseFileSystem::PreparePackFile(CSearchPath &packfile, int64_t offsetofpackinmetafile)
+{
+ packheader_t header;
+ FS_fseek(packfile.m_hPackFile->m_pFile, packfile.m_hPackFile->m_nStartOffset + offsetofpackinmetafile, FILESYSTEM_SEEK_HEAD);
+ FS_fread(&header, 1, sizeof(header), packfile.m_hPackFile->m_pFile);
+
+ if (*(uint32_t *)header.id == MAKEID('P', 'K', '6', '4')) {
+ return Prepare64BitPackFile(packfile, offsetofpackinmetafile);
+ }
+
+ if (*(uint32_t *)header.id != MAKEID('P', 'A', 'C', 'K'))
+ {
+ Warning(FILESYSTEM_WARNING, "%s is not a packfile\n", packfile.GetPath().String());
+ return false;
+ }
+
+ int numpackfiles = header.dirlen / sizeof(packfile_t);
+ if (numpackfiles > MAX_DIR_PACK_FILES) {
+ Warning(FILESYSTEM_WARNING, "%s has %i files\n", packfile.GetPath().String(), numpackfiles);
+ return false;
+ }
+
+ if (numpackfiles > 0)
+ {
+ packfile_t *newfiles = new packfile_t[numpackfiles];
+ if (!newfiles) {
+ Warning(FILESYSTEM_WARNING, "%s out of memory allocating directory for %i files\n", packfile.GetPath().String(), numpackfiles);
+ return false;
+ }
+
+ FS_fseek(packfile.m_hPackFile->m_pFile, packfile.m_hPackFile->m_nStartOffset + header.dirofs + offsetofpackinmetafile, FILESYSTEM_SEEK_HEAD);
+ Read(newfiles, header.dirlen, packfile.m_hPackFile->m_pFile);
+
+ for (int i = 0; i < numpackfiles; i++)
+ {
+ Q_strlwr(newfiles[i].name);
+ FixSlashes(newfiles[i].name);
+
+ CPackFileEntry lookup;
+ lookup.m_Name = newfiles[i].name;
+ lookup.m_nPosition = newfiles[i].filepos + offsetofpackinmetafile;
+ lookup.m_nLength = newfiles[i].filelen;
+
+ packfile.m_PackFiles.Insert(lookup);
+ }
+
+ packfile.m_nNumPackFiles = numpackfiles;
+ delete [] newfiles;
+ return true;
+ }
+
+ return false;
+}
+
+bool CBaseFileSystem::Prepare64BitPackFile(CSearchPath &packfile, int64_t offsetofpackinmetafile)
+{
+ packheader64_t header;
+ FS_fseek(packfile.m_hPackFile->m_pFile, packfile.m_hPackFile->m_nStartOffset + offsetofpackinmetafile, FILESYSTEM_SEEK_HEAD);
+ FS_fread(&header, 1, sizeof(header), packfile.m_hPackFile->m_pFile);
+
+ if (*(uint32_t *)header.id != MAKEID('P', 'K', '6', '4'))
+ {
+ Warning(FILESYSTEM_WARNING, "%s is not a packfile\n", packfile.GetPath().String());
+ return false;
+ }
+
+ int numpackfiles = header.dirlen / sizeof(packfile64_t);
+ if (numpackfiles > MAX_DIR_PACK_FILES) {
+ Warning(FILESYSTEM_WARNING, "%s has %i files\n", packfile.GetPath().String(), numpackfiles);
+ return false;
+ }
+
+ if (numpackfiles > 0)
+ {
+ packfile64_t *newfiles = new packfile64_t[numpackfiles];
+ if (!newfiles) {
+ Warning(FILESYSTEM_WARNING, "%s out of memory allocating directory for %i files\n", packfile.GetPath().String(), numpackfiles);
+ return false;
+ }
+
+ FS_fseek(packfile.m_hPackFile->m_pFile, packfile.m_hPackFile->m_nStartOffset + header.dirofs + offsetofpackinmetafile, FILESYSTEM_SEEK_HEAD);
+ Read(newfiles, header.dirlen, packfile.m_hPackFile->m_pFile);
+
+ for (int i = 0; i < numpackfiles; i++)
+ {
+ Q_strlwr(newfiles[i].name);
+ FixSlashes(newfiles[i].name);
+
+ CPackFileEntry lookup;
+ lookup.m_Name = newfiles[i].name;
+ lookup.m_nPosition = newfiles[i].filepos + offsetofpackinmetafile;
+ lookup.m_nLength = newfiles[i].filelen;
+
+ packfile.m_PackFiles.Insert(lookup);
+ }
+
+ packfile.m_nNumPackFiles = numpackfiles;
+ delete [] newfiles;
+ return true;
+ }
+
+ return false;
+}
+
+// Search pPath for pak?.pak files and add to search path if found
+void CBaseFileSystem::AddPackFiles(const char *pPath)
+{
+ int pakcount;
+ // determine pak files, [pak0..pakN]
+ for (pakcount = 0; ; pakcount++)
+ {
+ char pakfile[MAX_PATH], fullpath[MAX_PATH];
+ Q_sprintf(pakfile, "pak%i.pak", pakcount);
+ Q_sprintf(fullpath, "%s%s", pPath, pakfile);
+ FixSlashes(fullpath);
+
+ struct _stat buf;
+ if (FS_stat(fullpath, &buf) == -1)
+ break;
+ }
+
+ // Add any pack files in the format pak1.pak ... pak0.pak
+ // Add them backwards so pak(N) is higher priority than pak(N-1), etc.
+ for (int i = pakcount - 1; i >= 0; i--)
+ {
+ char pakfile[512];
+ Q_sprintf(pakfile, "pak%i.pak", i);
+ AddPackFileFromPath(pPath, pakfile, false, "");
+ }
+}
+
+// Wipe all map (.bsp) pak file search paths
+void CBaseFileSystem::RemoveAllMapSearchPaths()
+{
+ int c = m_SearchPaths.Count();
+ for (int i = c - 1; i >= 0; i--)
+ {
+ if (!m_SearchPaths[i].IsMapPath())
+ continue;
+
+ m_SearchPaths.Remove(i);
+ }
+}
+
+// Performs a simple case-insensitive string comparison, honoring trailing * wildcards
+bool IsWildCardMatch(const char *wildcardString, const char *stringToCheck)
+{
+ char wcChar;
+ char strChar;
+
+ if (!Q_strcmp(stringToCheck, ".") || !Q_strcmp(stringToCheck, ".."))
+ {
+ // ignore containing the levels directory
+ return false;
+ }
+
+ if (!Q_strcmp(wildcardString, "*.*") || !Q_strcmp(wildcardString, "*"))
+ {
+ // matches everything
+ return true;
+ }
+
+ // use the starMatchesZero variable to determine whether an asterisk
+ // matches zero or more characters (TRUE) or one or more characters (FALSE)
+ bool starMatchesZero = true;
+
+ while ((strChar = *stringToCheck) && (wcChar = *wildcardString))
+ {
+ // we only want to advance the pointers if we successfully assigned
+ // both of our char variables, so we'll do it here rather than in the
+ // loop condition itself
+ *stringToCheck++;
+ *wildcardString++;
+
+ // if this isn't a case-sensitive match, make both chars uppercase
+ wcChar = toupper(wcChar);
+ strChar = toupper(strChar);
+
+ // check the wcChar against our wildcard list
+ switch (wcChar)
+ {
+ // an asterisk matches zero or more characters
+ case '*' :
+ // do a recursive call against the rest of the string,
+ // until we've either found a match or the string has
+ // ended
+ if (starMatchesZero)
+ *stringToCheck--;
+
+ while (*stringToCheck)
+ {
+ if (IsWildCardMatch(wildcardString, stringToCheck++))
+ return true;
+ }
+
+ break;
+
+ // a question mark matches any single character
+ case '?' :
+ break;
+
+ // if we fell through, we want an exact match
+ default :
+ if (wcChar != strChar)
+ return false;
+ break;
+ }
+ }
+
+ // if we have any asterisks left at the end of the wildcard string, we can
+ // advance past them if starMatchesZero is TRUE (so "blah*" will match "blah")
+ while (*wildcardString && starMatchesZero)
+ {
+ if (*wildcardString == '*')
+ wildcardString++;
+ else
+ break;
+ }
+
+ // if we got to the end but there's still stuff left in either of our strings,
+ // return false; otherwise, we have a match
+ if (*stringToCheck || *wildcardString)
+ return false;
+ else
+ return true;
+}
+
+const char *CBaseFileSystem::SearchPakFile(const char *pWildCard, int currentSearchPathID, bool first)
+{
+ auto &curSearch = m_SearchPaths[currentSearchPathID];
+
+ if (first) {
+ curSearch.m_iCurSearchFile = 0;
+ }
+
+ while (curSearch.m_iCurSearchFile < curSearch.m_PackFiles.Count())
+ {
+ const char *file = curSearch.m_PackFiles[curSearch.m_iCurSearchFile++].m_Name.String();
+ if (IsWildCardMatch(pWildCard, file)) {
+ return file;
+ }
+ }
+
+ return nullptr;
+}
+
+const char *CBaseFileSystem::FindFirst(const char *pWildCard, FileFindHandle_t *pHandle, const char *pathID)
+{
+ Assert(pWildCard);
+ Assert(pHandle);
+
+ FileFindHandle_t hTmpHandle = m_FindData.AddToTail();
+ FindData_t *pFindData = &m_FindData[hTmpHandle];
+ Assert(pFindData);
+
+ int maxlen = Q_strlen(pWildCard) + 1;
+ pFindData->m_WildCardString.AddMultipleToTail(maxlen);
+ Q_strcpy(pFindData->m_WildCardString.Base(), pWildCard);
+ FixSlashes(pFindData->m_WildCardString.Base());
+
+ CUtlSymbol lookup(pathID);
+ int c = m_SearchPaths.Count();
+ for (pFindData->m_CurrentSearchPathID = 0; pFindData->m_CurrentSearchPathID < c; pFindData->m_CurrentSearchPathID++)
+ {
+ if (pathID && m_SearchPaths[pFindData->m_CurrentSearchPathID].GetPathID() != lookup) {
+ continue;
+ }
+
+ pFindData->m_LimitedPathID = pathID ? (UtlSymId_t)lookup : -1;
+
+ const char *fileName = FindFirstHelper(pWildCard, pHandle, pFindData->m_CurrentSearchPathID, pFindData);
+ if (fileName) {
+ return fileName;
+ }
+ }
+
+ // Handle failure here
+ pFindData = nullptr;
+ m_FindData.Remove(hTmpHandle);
+ return nullptr;
+}
+
+const char *CBaseFileSystem::FindFirstHelper(const char *pWildCard, FileFindHandle_t *pHandle, int searchPath, FindData_t *pFindData)
+{
+ CSearchPath *pSearchPath = &m_SearchPaths[searchPath];
+ if (pSearchPath->IsPackFile())
+ {
+ const char *fname = SearchPakFile(pFindData->m_WildCardString.Base(), searchPath);
+ if (fname) {
+ pFindData->m_FindHandle = INVALID_HANDLE_VALUE;
+ *pHandle = m_FindData.Count() - 1;
+ return fname;
+ }
+ }
+ else
+ {
+ int len = Q_strlen(pSearchPath->GetPath().String()) + pFindData->m_WildCardString.Size();
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, pSearchPath->GetPath().String());
+ Q_strcat(pTmpFileName, pFindData->m_WildCardString.Base());
+ FixSlashes(pTmpFileName);
+
+ if ((pFindData->m_FindHandle = FS_FindFirstFile(pTmpFileName, &pFindData->m_FindData)) != INVALID_HANDLE_VALUE)
+ {
+ *pHandle = m_FindData.Count() - 1;
+ return pFindData->m_FindData.cFileName;
+ }
+ }
+
+ return nullptr;
+}
+
+bool CBaseFileSystem::FileInSearchPaths(const char *pSearchWildcard, const char *pFileName, int minSearchPathID, int maxSearchPathID)
+{
+ if (minSearchPathID > maxSearchPathID)
+ return false;
+
+ const char *tmp = &pSearchWildcard[Q_strlen(pSearchWildcard) - 1];
+ for (; *tmp != CORRECT_PATH_SEPARATOR && pSearchWildcard < tmp; tmp--) {}
+
+ if (++tmp <= pSearchWildcard)
+ return false;
+
+ int pathStrLen = tmp - pSearchWildcard;
+ int fileNameStrLen = Q_strlen(pFileName);
+ char *pFileNameWithPath = (char *)alloca((pathStrLen + fileNameStrLen + 1) * sizeof(char));
+ Q_strncpy(pFileNameWithPath, pSearchWildcard, pathStrLen);
+ pFileNameWithPath[pathStrLen] = '\0';
+ Q_strcat(pFileNameWithPath, pFileName);
+
+ for (int i = minSearchPathID; i <= maxSearchPathID; i++)
+ {
+ CSearchPath *pSearchPath = &m_SearchPaths[i];
+
+ if (!pSearchPath->IsPackFile())
+ {
+ int len = Q_strlen(pFileNameWithPath) + Q_strlen(pSearchPath->GetPath().String());
+ char *fullFilePath = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(fullFilePath, pSearchPath->GetPath().String());
+ Q_strcat(fullFilePath, pFileNameWithPath);
+
+ struct _stat buf;
+ if (FS_stat(fullFilePath, &buf) != -1) {
+ return true;
+ }
+ }
+ else
+ {
+ int curSearch = pSearchPath->m_iCurSearchFile;
+ bool ret = SearchPakFile(pSearchWildcard, i) ? true : false;
+ pSearchPath->m_iCurSearchFile = curSearch;
+
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+
+ return false;
+}
+
+// Get the next file, trucking through the path. Don't check for duplicates.
+bool CBaseFileSystem::FindNextFileHelper(FindData_t *pFindData)
+{
+ if (m_SearchPaths[pFindData->m_CurrentSearchPathID].IsPackFile())
+ {
+ const char *file = SearchPakFile(pFindData->m_WildCardString.Base(), pFindData->m_CurrentSearchPathID, false);
+ if (file)
+ {
+ char *pFileNameNoPath = const_cast(Q_strrchr(file, CORRECT_PATH_SEPARATOR));
+ if (pFileNameNoPath)
+ {
+ file = pFileNameNoPath + 1;
+ }
+
+ Q_strlcpy(pFindData->m_FindData.cFileName, file);
+ FixSlashes(pFindData->m_FindData.cFileName);
+ return true;
+ }
+ }
+ // Try the same search path that we were already searching on
+ else if (FS_FindNextFile(pFindData->m_FindHandle, &pFindData->m_FindData))
+ {
+ return true;
+ }
+
+ // This happens when we searched a full path
+ // if (pFindData->m_CurrentSearchPathID < 0)
+ // return false;
+
+ pFindData->m_CurrentSearchPathID++;
+
+ if (pFindData->m_FindHandle != INVALID_HANDLE_VALUE)
+ {
+ FS_FindClose(pFindData->m_FindHandle);
+ }
+
+ pFindData->m_FindHandle = INVALID_HANDLE_VALUE;
+
+ int c = m_SearchPaths.Count();
+ for(; pFindData->m_CurrentSearchPathID < c; pFindData->m_CurrentSearchPathID++)
+ {
+ CSearchPath *pSearchPath = &m_SearchPaths[pFindData->m_CurrentSearchPathID];
+
+ if (pFindData->m_LimitedPathID != -1)
+ {
+ if (pSearchPath->GetPath() != pFindData->m_LimitedPathID) {
+ return false;
+ }
+ }
+
+ if (pSearchPath->IsPackFile())
+ {
+ const char *file = SearchPakFile(pFindData->m_WildCardString.Base(), pFindData->m_CurrentSearchPathID);
+ if (file)
+ {
+ Q_strlcpy(pFindData->m_FindData.cFileName, Q_strrchr(file, CORRECT_PATH_SEPARATOR) + 1);
+ FixSlashes(pFindData->m_FindData.cFileName);
+ return true;
+ }
+ }
+ else
+ {
+ int len = Q_strlen(pSearchPath->GetPath().String() + pFindData->m_WildCardString.Size());
+ char *pTmpFileName = (char *)alloca((len + 1) * sizeof(char));
+ Q_strcpy(pTmpFileName, pSearchPath->GetPath().String());
+ Q_strcat(pTmpFileName, pFindData->m_WildCardString.Base());
+ FixSlashes(pTmpFileName);
+
+ if ((pFindData->m_FindHandle = FS_FindFirstFile(pTmpFileName, &pFindData->m_FindData)) != INVALID_HANDLE_VALUE)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+const char *CBaseFileSystem::FindNext(FileFindHandle_t handle)
+{
+ if (!m_FindData.Count() || handle >= m_FindData.Count()) {
+ return nullptr;
+ }
+
+ FindData_t *pFindData = &m_FindData[handle];
+ if (FindNextFileHelper(pFindData))
+ {
+ return pFindData->m_FindData.cFileName;
+ }
+
+ return nullptr;
+}
+
+bool CBaseFileSystem::FindIsDirectory(FileFindHandle_t handle)
+{
+ FindData_t *pFindData = &m_FindData[handle];
+ return (pFindData->m_FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
+}
+
+void CBaseFileSystem::FindClose(FileFindHandle_t handle)
+{
+ if (!m_FindData.Count() || handle >= m_FindData.Count())
+ return;
+
+ FindData_t *pFindData = &m_FindData[handle];
+ Assert(pFindData);
+
+ if (pFindData->m_FindHandle != INVALID_HANDLE_VALUE)
+ {
+ FS_FindClose(pFindData->m_FindHandle);
+ }
+
+ pFindData->m_FindHandle = INVALID_HANDLE_VALUE;
+
+ pFindData->m_WildCardString.Purge();
+ m_FindData.FastRemove(handle);
+}
+
+char *CBaseFileSystem::ParseFile(char *pFileBytes, char *pToken, bool *pWasQuoted)
+{
+ return ::ParseFile(pFileBytes, pToken, pWasQuoted);
+}
+
+long CBaseFileSystem::GetFileTime(const char *pFileName)
+{
+ for (int i = 0; i < m_SearchPaths.Count(); i++)
+ {
+ FileHandle_t filehandle = FindFile(&m_SearchPaths[i], pFileName, "rb");
+ if (filehandle)
+ {
+ CFileHandle *fh = reinterpret_cast(filehandle);
+ long time = fh->m_nFileTime;
+ Close(filehandle);
+ return time;
+ }
+ }
+
+ return 0L;
+}
+
+void CBaseFileSystem::FileTimeToString(char *pString, int maxCharsIncludingTerminator, long fileTime)
+{
+ time_t time = fileTime;
+ Q_strncpy(pString, ctime(&time), maxCharsIncludingTerminator);
+ pString[maxCharsIncludingTerminator - 1] = '\0';
+}
+
+void CBaseFileSystem::SetWarningFunc(WarningFunc_t pfnWarning)
+{
+ m_pfnWarning = pfnWarning;
+}
+
+void CBaseFileSystem::SetWarningLevel(FileWarningLevel_t level)
+{
+ m_fwLevel = level;
+}
+
+void CBaseFileSystem::Warning(FileWarningLevel_t level, const char *fmt, ...)
+{
+ if (m_fwLevel < level) {
+ return;
+ }
+
+ va_list argptr;
+ char warningtext[4096];
+
+ va_start(argptr, fmt);
+ Q_vsnprintf(warningtext, sizeof(warningtext), fmt, argptr);
+ va_end(argptr);
+
+ if (m_pfnWarning)
+ {
+ m_pfnWarning(warningtext);
+ }
+ else
+ {
+#ifdef _WIN32
+ OutputDebugString(warningtext);
+#else
+ fprintf(stderr, warningtext);
+#endif
+ }
+}
+
+bool CBaseFileSystem::FullPathToRelativePath(const char *pFullpath, char *pRelative)
+{
+ int inlen = Q_strlen(pFullpath);
+ if (inlen <= 0) {
+ *pRelative = '\0';
+ return false;
+ }
+
+ Q_strcpy(pRelative, pFullpath);
+
+ char *inpath = (char *)alloca((inlen + 1) * sizeof(char));
+ Q_strcpy(inpath, pFullpath);
+
+#ifdef _WIN32 // don't do this on linux!
+ Q_strlwr(inpath);
+#endif
+
+ FixSlashes(inpath);
+
+ bool success = false;
+ for (int i = 0; i < m_SearchPaths.Count() && !success; i++)
+ {
+ CSearchPath *pSearchPath = &m_SearchPaths[i];
+ if (pSearchPath->IsMapPath())
+ continue;
+
+ char *searchbase = new char [Q_strlen(pSearchPath->GetPath().String()) + 1];
+ Q_strcpy(searchbase, pSearchPath->GetPath().String());
+ FixSlashes(searchbase);
+
+#ifdef _WIN32 // don't do this on linux!
+ Q_strlwr(searchbase);
+#endif
+
+ int baselen = Q_strlen(searchbase);
+ if (!Q_strnicmp(searchbase, inpath, baselen))
+ {
+ success = true;
+ Q_strcpy(pRelative, &inpath[baselen]);
+ }
+
+ delete [] searchbase;
+ }
+
+ return false;
+}
+
+bool CBaseFileSystem::OpenedFileLessFunc(COpenedFile const &src1, COpenedFile const &src2)
+{
+ return src1.m_pFile < src2.m_pFile;
+}
+
+void CBaseFileSystem::RemoveAllSearchPaths()
+{
+ m_SearchPaths.Purge();
+ m_PackFileHandles.Purge();
+}
+
+void CBaseFileSystem::StripFilename(char *path)
+{
+ int length = Q_strlen(path) - 1;
+ while (length > 0)
+ {
+ if (path[length] == CORRECT_PATH_SEPARATOR
+ || path[length] == INCORRECT_PATH_SEPARATOR)
+ break;
+
+ length--;
+ }
+
+ path[length] = '\0';
+}
+
+void CBaseFileSystem::FixSlashes(char *str)
+{
+ auto ptr = str;
+ while (*str)
+ {
+ if (*str == INCORRECT_PATH_SEPARATOR) {
+ *str = CORRECT_PATH_SEPARATOR;
+ }
+
+ str++;
+ }
+}
+
+void CBaseFileSystem::FixPath(char *str)
+{
+ char *lastChar = &str[Q_strlen(str) - 1];
+ if (*lastChar != CORRECT_PATH_SEPARATOR && *lastChar != INCORRECT_PATH_SEPARATOR)
+ {
+ lastChar[1] = CORRECT_PATH_SEPARATOR;
+ lastChar[2] = '\0';
+ }
+
+ FixSlashes(str);
+}
+
+CBaseFileSystem::COpenedFile::COpenedFile() : m_pFile(nullptr), m_pName(nullptr)
+{
+}
+
+CBaseFileSystem::COpenedFile::~COpenedFile()
+{
+ if (m_pName) {
+ delete[] m_pName;
+ m_pName = nullptr;
+ }
+}
+
+CBaseFileSystem::COpenedFile::COpenedFile(COpenedFile const &src)
+{
+ m_pFile = src.m_pFile;
+ m_pName = nullptr;
+
+ if (src.m_pName)
+ {
+ m_pName = new char [Q_strlen(src.m_pName) + 1];
+ Q_strcpy(m_pName, src.m_pName);
+ }
+}
+
+bool CBaseFileSystem::COpenedFile::operator==(COpenedFile const &src) const
+{
+ return m_pFile == src.m_pFile;
+}
+
+void CBaseFileSystem::COpenedFile::SetName(const char *name)
+{
+ if (m_pName) {
+ delete[] m_pName;
+ }
+
+ m_pName = new char [Q_strlen(name) + 1];
+ Q_strcpy(m_pName, name);
+}
+
+const char *CBaseFileSystem::COpenedFile::GetName()
+{
+ return m_pName ? m_pName : "???";
+}
+
+bool CBaseFileSystem::CSearchPath::PackFileLessFunc(CPackFileEntry const &src1, CPackFileEntry const &src2)
+{
+ return src1.m_Name < src2.m_Name;
+}
+
+CBaseFileSystem::CSearchPath::CSearchPath() :
+ m_PackFiles(0, MAX_ENTRY_PATH, CSearchPath::PackFileLessFunc),
+ m_Path(CUtlSymbol("")),
+ m_bIsMapPath(false),
+ m_bIsPackFile(false),
+ m_bAllowWrite(true),
+ m_lPackFileTime(0),
+ m_nNumPackFiles(0),
+ m_iCurSearchFile(0),
+ m_hPackFile(nullptr)
+{
+}
+
+CBaseFileSystem::CSearchPath::~CSearchPath()
+{
+ if (m_bIsPackFile && m_hPackFile)
+ {
+ BaseFileSystem()->m_PackFileHandles.FindAndRemove(m_hPackFile->m_pFile);
+ BaseFileSystem()->Close(m_hPackFile->m_pFile);
+ }
+}
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.h b/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.h
new file mode 100644
index 0000000..5ae37e8
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/BaseFileSystem.h
@@ -0,0 +1,378 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#pragma once
+
+#include
+#include "filesystem_helpers.h"
+
+#ifdef _WIN32
+ #include // mkdir
+#else
+ #include // unlink
+ #include "linux_support.h"
+
+ // undo the prepended "_" 's
+ #define _chmod chmod
+ #define _stat stat
+ #define _alloca alloca
+ #define _S_IFDIR S_IFDIR
+
+ #define HANDLE int
+ #define INVALID_HANDLE_VALUE ((HANDLE)~0)
+#endif // _WIN32
+
+#include "utlsymbol.h"
+
+class CBaseFileSystem: public IFileSystem {
+public:
+ CBaseFileSystem();
+ virtual ~CBaseFileSystem() {};
+
+ // Mount and unmount the filesystem
+ virtual void Mount();
+ virtual void Unmount();
+
+ // opens a file
+ // if pathID is NULL, all paths will be searched for the file
+ virtual FileHandle_t Open(const char *pFileName, const char *pOptions, const char *pathID = 0L);
+
+ // open a file but force the data to come from the steam cache, NOT from disk
+ virtual FileHandle_t OpenFromCacheForRead(const char *pFileName, const char *pOptions, const char *pathID = 0L);
+
+ virtual void Close(FileHandle_t file);
+
+ virtual void Seek(FileHandle_t file, int pos, FileSystemSeek_t seekType);
+ virtual size_t Tell(FileHandle_t file);
+
+ virtual size_t Size(FileHandle_t file);
+ virtual size_t Size(const char *pFileName);
+
+ virtual bool IsOk(FileHandle_t file);
+
+ virtual void Flush(FileHandle_t file);
+ virtual bool EndOfFile(FileHandle_t file);
+
+ virtual int Read(void* pOutput, int size, FileHandle_t file);
+ virtual int Write(void const* pInput, int size, FileHandle_t file);
+ virtual char *ReadLine(char *pOutput, int maxChars, FileHandle_t file);
+ virtual int FPrintf(FileHandle_t file, const char *fmt, ...);
+
+ // direct filesystem buffer access
+ // returns a handle to a buffer containing the file data
+ // this is the optimal way to access the complete data for a file,
+ // since the file preloader has probably already got it in memory
+ virtual void *GetReadBuffer(FileHandle_t file, int *outBufferSize, bool failIfNotInCache);
+ virtual void ReleaseReadBuffer(FileHandle_t file, void *readBuffer);
+
+ // Gets the current working directory
+ virtual bool GetCurrentDirectory(char* pDirectory, int maxlen);
+
+ // this isn't implementable on STEAM as is.
+ virtual void CreateDirHierarchy(const char *path, const char *pathID);
+
+ // File I/O and info
+ virtual bool IsDirectory(const char *pFileName);
+ virtual const char *GetLocalPath(const char *pFileName, char *pLocalPath, int localPathBufferSize);
+
+ // Deletes a file
+ virtual void RemoveFile(const char *pRelativePath, const char *pathID);
+
+ // Remove all search paths (including write path?)
+ virtual void RemoveAllSearchPaths();
+
+ // Add paths in priority order (mod dir, game dir, ....)
+ // If one or more .pak files are in the specified directory, then they are
+ // added after the file system path
+ // If the path is the relative path to a .bsp file, then any previous .bsp file
+ // override is cleared and the current .bsp is searched for an embedded PAK file
+ // and this file becomes the highest priority search path (i.e., it's looked at first
+ // even before the mod's file system path).
+ virtual void AddSearchPath(const char *pPath, const char *pathID);
+ virtual bool RemoveSearchPath(const char *pPath);
+
+ virtual bool FileExists(const char *pFileName);
+
+ virtual long GetFileTime(const char *pFileName);
+ virtual void FileTimeToString(char* pStrip, int maxCharsIncludingTerminator, long fileTime);
+
+ // FindFirst/FindNext
+ virtual const char *FindFirst(const char *pWildCard, FileFindHandle_t *pHandle, const char *pathID = 0L);
+ virtual const char *FindNext(FileFindHandle_t handle);
+ virtual bool FindIsDirectory(FileFindHandle_t handle);
+ virtual void FindClose(FileFindHandle_t handle);
+
+ // Note: This is sort of a secondary feature; but it's really useful to have it here
+ virtual char *ParseFile(char* pFileBytes, char* pToken, bool* pWasQuoted);
+
+ // Returns true on success (based on current list of search paths, otherwise false if it can't be resolved)
+ virtual bool FullPathToRelativePath(const char *pFullpath, char *pRelative);
+
+ // Dump to printf/OutputDebugString the list of files that have not been closed
+ virtual void PrintOpenedFiles();
+
+ virtual void SetWarningFunc(WarningFunc_t pfnWarning);
+ virtual void SetWarningLevel(FileWarningLevel_t level);
+
+ // interface for custom pack files > 4Gb
+ virtual bool AddPackFile(const char *fullpath, const char *pathID);
+
+ FILE *Trace_FOpen(const char *filename, const char *options, bool bFromCache = false);
+ void Trace_FClose(FILE *fp);
+ void Trace_DumpUnclosedFiles();
+
+ // Purpose: Functions implementing basic file system behavior.
+ virtual FILE *FS_fopen(const char *filename, const char *options, bool bFromCache = false) = 0;
+ virtual void FS_fclose(FILE *fp) = 0;
+ virtual void FS_fseek(FILE *fp, int64_t pos, FileSystemSeek_t seekType) = 0;
+ virtual size_t FS_ftell(FILE *fp) = 0;
+ virtual int FS_feof(FILE *fp) = 0;
+ virtual size_t FS_fread(void *dest, size_t count, size_t size, FILE *fp) = 0;
+ virtual size_t FS_fwrite(const void *src, size_t count, size_t size, FILE *fp) = 0;
+ virtual size_t FS_vfprintf(FILE *fp, const char *fmt, va_list list) = 0;
+ virtual int FS_ferror(FILE *fp) = 0;
+ virtual int FS_fflush(FILE *fp) = 0;
+ virtual char *FS_fgets(char *dest, int destSize, FILE *fp) = 0;
+ virtual int FS_stat(const char *path, struct _stat *buf) = 0;
+ virtual HANDLE FS_FindFirstFile(char *findname, WIN32_FIND_DATA *dat) = 0;
+ virtual bool FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat) = 0;
+ virtual bool FS_FindClose(HANDLE handle) = 0;
+ virtual bool IsThreadSafe() = 0;
+ virtual bool IsFileImmediatelyAvailable(const char *pFileName) = 0;
+
+protected:
+ // structures for building pack files
+ #pragma pack(1)
+ typedef struct
+ {
+ char name[56];
+ int filepos;
+ int filelen;
+ } packfile_t;
+
+ typedef struct
+ {
+ char id[4];
+ int dirofs;
+ int dirlen;
+ } packheader_t;
+
+ typedef struct
+ {
+ char name[112];
+ int64_t filepos;
+ int64_t filelen;
+ } packfile64_t;
+
+ typedef struct
+ {
+ char id[4];
+ int64_t dirofs;
+ int64_t dirlen;
+ } packheader64_t;
+
+ typedef struct
+ {
+ char id[8];
+ int64_t packheaderpos;
+ int64_t originalfilesize;
+ } packappenededheader_t;
+ #pragma pack()
+
+ // A Pack file directory entry:
+ class CPackFileEntry
+ {
+ public:
+ CUtlSymbol m_Name;
+ int64_t m_nPosition;
+ int64_t m_nLength;
+ };
+
+ class CFileHandle
+ {
+ public:
+ FILE* m_pFile;
+ bool m_bPack;
+ bool m_bErrorFlagged;
+ int64_t m_nStartOffset;
+ int64_t m_nLength;
+ long m_nFileTime;
+ };
+
+ enum { MAX_FILES_IN_PACK = 32768 };
+ class CSearchPath
+ {
+ public:
+ CSearchPath();
+ ~CSearchPath();
+
+ // Path ID ("game", "mod", "gamebin") accessors.
+ void SetPathID(CUtlSymbol id);
+ const CUtlSymbol &GetPathID() const;
+
+ // Search path (c:\hl\hl) accessors.
+ void SetPath(CUtlSymbol id);
+ const CUtlSymbol &GetPath() const;
+
+ void SetWritePath(bool st);
+
+ bool IsAllowWrite() const;
+ bool IsPackFile() const;
+ bool IsMapPath() const;
+
+ private:
+ friend class CBaseFileSystem;
+
+ CUtlSymbol m_Path;
+ CUtlSymbol m_PathID;
+
+ bool m_bIsMapPath;
+ bool m_bIsPackFile;
+
+ long m_lPackFileTime;
+ CFileHandle* m_hPackFile;
+ int m_nNumPackFiles;
+ size_t m_iCurSearchFile;
+ bool m_bAllowWrite;
+
+ enum { MAX_ENTRY_PATH = 32 };
+
+ // Entries to the individual files stored inside the pack file.
+ CUtlRBTree m_PackFiles;
+ static bool PackFileLessFunc(CPackFileEntry const &src1, CPackFileEntry const &src2);
+ };
+
+ // Purpose: For tracking unclosed files
+ // NOTE: The symbol table could take up memory that we don't want to eat here.
+ // In that case, we shouldn't store them in a table, or we should store them as locally allocates stings
+ // so we can control the size
+ class COpenedFile
+ {
+ public:
+ COpenedFile();
+ ~COpenedFile();
+
+ COpenedFile(COpenedFile const &src);
+ bool operator==(COpenedFile const &src) const;
+
+ void SetName(char const *name);
+ const char *GetName();
+
+ FILE *m_pFile;
+ char *m_pName;
+ };
+
+ struct FindData_t
+ {
+ WIN32_FIND_DATA m_FindData;
+ int m_CurrentSearchPathID;
+ int m_LimitedPathID;
+ CUtlVector m_WildCardString;
+ HANDLE m_FindHandle;
+ };
+
+ virtual void AddSearchPathNoWrite(const char *pPath, const char *pathID);
+ void AddSearchPathInternal(const char *pPath, const char *pathID, bool bAllowWrite);
+
+ void Warning(FileWarningLevel_t level, const char *fmt, ...);
+ void FixSlashes(char *str);
+ void FixPath(char *str);
+ void StripFilename(char *path);
+ CSearchPath *GetWritePath(const char *pathID);
+ FileHandle_t FindFile(CSearchPath *path, const char *pFileName, const char *pOptions, bool bFromCache = false);
+ int FastFindFileSize(CSearchPath *path, const char *pFileName);
+ void RemoveAllMapSearchPaths();
+ void AddPackFiles(const char *pPath);
+ bool AddPackFileFromPath(const char *pPath, const char *pakfile, bool bCheckForAppendedPack, const char *pathID);
+ bool PreparePackFile(CSearchPath &packfile, int64_t offsetofpackinmetafile);
+ bool Prepare64BitPackFile(CSearchPath &packfile, int64_t offsetofpackinmetafile);
+ const char *SearchPakFile(const char *pWildCard, int currentSearchPathID, bool first = true);
+ bool FileInSearchPaths(const char *pSearchWildcard, const char *pFileName, int minSearchPathID, int maxSearchPathID);
+ bool FindNextFileHelper(FindData_t *pFindData);
+ const char *FindFirstHelper(const char *pWildCard, FileFindHandle_t *pHandle, int searchPath, FindData_t *pFindData);
+
+public:
+ static CBaseFileSystem *s_pFileSystem;
+
+protected:
+ CUtlVector m_OpenedFiles;
+ static bool OpenedFileLessFunc(COpenedFile const &src1, COpenedFile const &src2);
+
+ CUtlVector m_PackFileHandles;
+ CUtlVector m_FindData;
+ CUtlVector m_SearchPaths;
+
+ FileWarningLevel_t m_fwLevel;
+ WarningFunc_t m_pfnWarning;
+
+ int m_nOpenCount;
+};
+
+// singleton accessor
+CBaseFileSystem *BaseFileSystem();
+
+// Inlines
+inline void CBaseFileSystem::CSearchPath::SetPathID(CUtlSymbol sym)
+{
+ m_PathID = sym;
+}
+
+inline const CUtlSymbol &CBaseFileSystem::CSearchPath::GetPathID() const
+{
+ return m_PathID;
+}
+
+inline void CBaseFileSystem::CSearchPath::SetPath(CUtlSymbol id)
+{
+ m_Path = id;
+}
+
+inline const CUtlSymbol &CBaseFileSystem::CSearchPath::GetPath() const
+{
+ return m_Path;
+}
+
+inline void CBaseFileSystem::CSearchPath::SetWritePath(bool st)
+{
+ m_bAllowWrite = st;
+}
+
+inline bool CBaseFileSystem::CSearchPath::IsAllowWrite() const
+{
+ return m_bAllowWrite;
+}
+
+inline bool CBaseFileSystem::CSearchPath::IsPackFile() const
+{
+ return m_bIsPackFile;
+}
+
+inline bool CBaseFileSystem::CSearchPath::IsMapPath() const
+{
+ return m_bIsMapPath;
+}
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.cpp b/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.cpp
new file mode 100644
index 0000000..78d1188
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.cpp
@@ -0,0 +1,232 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#include "precompiled.h"
+
+CFileSystem_Stdio::CFileSystem_Stdio()
+{
+ m_bMounted = false;
+}
+
+CFileSystem_Stdio::~CFileSystem_Stdio()
+{
+ RemoveAllSearchPaths();
+ Trace_DumpUnclosedFiles();
+}
+
+void CFileSystem_Stdio::Mount()
+{
+ m_bMounted = true;
+ CBaseFileSystem::Mount();
+}
+
+void CFileSystem_Stdio::Unmount()
+{
+ m_bMounted = false;
+ CBaseFileSystem::Unmount();
+}
+
+// low-level filesystem wrapper
+FILE *CFileSystem_Stdio::FS_fopen(const char *filename, const char *options, bool bFromCache)
+{
+ FILE *tst = fopen(filename, options);
+
+#ifndef _WIN32
+ if (!tst && !Q_strchr(options, 'w') && !Q_strchr(options, '+')) {
+ const char *file = findFileInDirCaseInsensitive(filename);
+ tst = fopen(filename, options);
+ }
+#endif // _WIN32
+
+ return tst;
+}
+
+void CFileSystem_Stdio::FS_fclose(FILE *fp)
+{
+ fclose(fp);
+}
+
+void CFileSystem_Stdio::FS_fseek(FILE *fp, int64_t pos, FileSystemSeek_t seekType)
+{
+ fseek(fp, pos, seekType);
+}
+
+size_t CFileSystem_Stdio::FS_ftell(FILE *fp)
+{
+ return ftell(fp);
+}
+
+int CFileSystem_Stdio::FS_feof(FILE *fp)
+{
+ return feof(fp);
+}
+
+size_t CFileSystem_Stdio::FS_fread(void *dest, size_t count, size_t size, FILE *fp)
+{
+ return fread(dest, count, size, fp);
+}
+
+size_t CFileSystem_Stdio::FS_fwrite(const void *src, size_t count, size_t size, FILE *fp)
+{
+ return fwrite(src, count, size, fp);
+}
+
+size_t CFileSystem_Stdio::FS_vfprintf(FILE *fp, const char *fmt, va_list list)
+{
+ return vfprintf(fp, fmt, list);
+}
+
+int CFileSystem_Stdio::FS_ferror(FILE *fp)
+{
+ return ferror(fp);
+}
+
+int CFileSystem_Stdio::FS_fflush(FILE *fp)
+{
+ return fflush(fp);
+}
+
+char *CFileSystem_Stdio::FS_fgets(char *dest, int destSize, FILE *fp)
+{
+ return fgets(dest, destSize, fp);
+}
+
+int CFileSystem_Stdio::FS_stat(const char *path, struct _stat *buf)
+{
+ int rt = _stat(path, buf);
+
+#ifndef _WIN32
+ if (rt == -1)
+ {
+ const char *file = findFileInDirCaseInsensitive(path);
+ if (file) {
+ rt = _stat(file, buf);
+ }
+ }
+#endif // _WIN32
+
+ return rt;
+}
+
+HANDLE CFileSystem_Stdio::FS_FindFirstFile(char *findname, WIN32_FIND_DATA *dat)
+{
+ return ::FindFirstFile(findname, dat);
+}
+
+bool CFileSystem_Stdio::FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat)
+{
+ return (::FindNextFile(handle, dat) != 0);
+}
+
+bool CFileSystem_Stdio::FS_FindClose(HANDLE handle)
+{
+ return (::FindClose(handle) != 0);
+}
+
+bool CFileSystem_Stdio::IsThreadSafe()
+{
+ return true;
+}
+
+bool CFileSystem_Stdio::IsFileImmediatelyAvailable(const char *pFileName)
+{
+ return true;
+}
+
+void CFileSystem_Stdio::GetLocalCopy(const char *pFileName)
+{
+ ;
+}
+
+void CFileSystem_Stdio::LogLevelLoadFinished(const char *name)
+{
+ ;
+}
+
+void CFileSystem_Stdio::LogLevelLoadStarted(const char *name)
+{
+ ;
+}
+
+int CFileSystem_Stdio::HintResourceNeed(const char *hintlist, int forgetEverything)
+{
+ return 0;
+}
+
+int CFileSystem_Stdio::PauseResourcePreloading()
+{
+ return 0;
+}
+
+int CFileSystem_Stdio::ResumeResourcePreloading()
+{
+ return 0;
+}
+
+int CFileSystem_Stdio::SetVBuf(FileHandle_t stream, char *buffer, int mode, long size)
+{
+ CFileHandle *fh = reinterpret_cast(stream);
+ if (!fh) {
+ Warning(FILESYSTEM_WARNING, "FS: Tried to SetVBuf NULL file handle!\n");
+ return 0;
+ }
+
+ return setvbuf(fh->m_pFile, buffer, mode, size);
+}
+
+void CFileSystem_Stdio::GetInterfaceVersion(char *p, int maxlen)
+{
+ Q_strncpy(p, "Stdio", maxlen);
+}
+
+WaitForResourcesHandle_t CFileSystem_Stdio::WaitForResources(const char *resourcelist)
+{
+ return (WaitForResourcesHandle_t)FILESYSTEM_INVALID_HANDLE;
+}
+
+bool CFileSystem_Stdio::GetWaitForResourcesProgress(WaitForResourcesHandle_t handle, float *progress, bool *complete)
+{
+ if (progress) *progress = 0;
+ if (complete) *complete = true;
+
+ return false;
+}
+
+void CFileSystem_Stdio::CancelWaitForResources(WaitForResourcesHandle_t handle)
+{
+ ;
+}
+
+bool CFileSystem_Stdio::IsAppReadyForOfflinePlay(int appID)
+{
+ return true;
+}
+
+#ifndef HOOK_FILESYSTEM
+EXPOSE_SINGLE_INTERFACE(CFileSystem_Stdio, IFileSystem, FILESYSTEM_INTERFACE_VERSION);
+#endif // HOOK_FILESYSTEM
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.h b/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.h
new file mode 100644
index 0000000..afe81e0
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/FileSystem_Stdio.h
@@ -0,0 +1,91 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#pragma once
+
+#include "BaseFileSystem.h"
+#include "linux_support.h"
+
+class CFileSystem_Stdio: public CBaseFileSystem
+{
+public:
+ CFileSystem_Stdio();
+ virtual ~CFileSystem_Stdio();
+
+ // Mount and unmount the filesystem
+ virtual void Mount();
+ virtual void Unmount();
+
+ virtual void GetLocalCopy(const char *pFileName);
+
+ virtual void LogLevelLoadStarted(const char *name);
+ virtual void LogLevelLoadFinished(const char *name);
+ virtual int HintResourceNeed(const char *hintlist, int forgetEverything);
+ virtual int PauseResourcePreloading();
+ virtual int ResumeResourcePreloading();
+ virtual int SetVBuf(FileHandle_t stream, char *buffer, int mode, long size);
+ virtual void GetInterfaceVersion(char *p, int maxlen);
+
+ // starts waiting for resources to be available
+ // returns FILESYSTEM_INVALID_HANDLE if there is nothing to wait on
+ virtual WaitForResourcesHandle_t WaitForResources(const char *resourcelist);
+
+ // get progress on waiting for resources; progress is a float [0, 1], complete is true on the waiting being done
+ // returns false if no progress is available
+ // any calls after complete is true or on an invalid handle will return false, 0.0f, true
+ virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t handle, float *progress /* out */ , bool *complete /* out */);
+
+ // cancels a progress call
+ virtual void CancelWaitForResources(WaitForResourcesHandle_t handle);
+
+ // returns true if the appID has all its caches fully preloaded
+ virtual bool IsAppReadyForOfflinePlay(int appID);
+
+protected:
+ // implementation of CBaseFileSystem virtual functions
+ virtual FILE *FS_fopen(const char *filename, const char *options, bool bFromCache = false);
+ virtual void FS_fclose(FILE *fp);
+ virtual void FS_fseek(FILE *fp, int64_t pos, FileSystemSeek_t seekType);
+ virtual size_t FS_ftell(FILE *fp);
+ virtual int FS_feof(FILE *fp);
+ virtual size_t FS_fread(void *dest, size_t count, size_t size, FILE *fp);
+ virtual size_t FS_fwrite(const void *src, size_t count, size_t size, FILE *fp);
+ virtual size_t FS_vfprintf(FILE *fp, const char *fmt, va_list list);
+ virtual int FS_ferror(FILE *fp);
+ virtual int FS_fflush(FILE *fp);
+ virtual char *FS_fgets(char *dest, int destSize, FILE *fp);
+ virtual int FS_stat(const char *path, struct _stat *buf);
+ virtual HANDLE FS_FindFirstFile(char *findname, WIN32_FIND_DATA *dat);
+ virtual bool FS_FindNextFile(HANDLE handle, WIN32_FIND_DATA *dat);
+ virtual bool FS_FindClose(HANDLE handle);
+ virtual bool IsThreadSafe();
+ virtual bool IsFileImmediatelyAvailable(const char *pFileName);
+
+private:
+ bool m_bMounted;
+};
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.cpp b/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.cpp
new file mode 100644
index 0000000..097fd60
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.cpp
@@ -0,0 +1,178 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#include "precompiled.h"
+
+// wordbreak parsing set
+characterset_t g_BreakSet, g_BreakSetIncludingColons;
+
+void InitializeCharacterSets()
+{
+ static bool s_CharacterSetInitialized = false;
+ if (!s_CharacterSetInitialized)
+ {
+ CharacterSetBuild(&g_BreakSet, "{}()'");
+ CharacterSetBuild(&g_BreakSetIncludingColons, "{}()':");
+ s_CharacterSetInitialized = true;
+ }
+}
+
+const char *ParseFile(const char *pFileBytes, char *pToken, bool *pWasQuoted, characterset_t *pCharSet)
+{
+ if (pWasQuoted)
+ *pWasQuoted = false;
+
+ if (!pFileBytes)
+ return nullptr;
+
+ InitializeCharacterSets();
+
+ // YWB: Ignore colons as token separators in COM_Parse
+ static const bool com_ignorecolons = false;
+ characterset_t &breaks = pCharSet ? *pCharSet : (com_ignorecolons ? g_BreakSet : g_BreakSetIncludingColons);
+
+ int c;
+ int len = 0;
+ pToken[0] = '\0';
+
+skipwhite:
+ // skip whitespace
+ while ((c = *pFileBytes) <= ' ')
+ {
+ if (c == 0)
+ {
+ // end of file;
+ return nullptr;
+ }
+
+ pFileBytes++;
+ }
+
+ // skip // comments till the next line
+ if (c == '/' && pFileBytes[1] == '/')
+ {
+ while (*pFileBytes && *pFileBytes != '\n')
+ pFileBytes++;
+ goto skipwhite; // start over new line
+ }
+
+ // skip c-style comments
+ if (c == '/' && pFileBytes[1] == '*')
+ {
+ // Skip "/*"
+ pFileBytes += 2;
+
+ while (*pFileBytes)
+ {
+ if (*pFileBytes == '*' && pFileBytes[1] == '/')
+ {
+ pFileBytes += 2;
+ break;
+ }
+
+ pFileBytes++;
+ }
+
+ goto skipwhite;
+ }
+
+ // handle quoted strings specially: copy till the end or another quote
+ if (c == '\"')
+ {
+ if (pWasQuoted)
+ *pWasQuoted = true;
+
+ pFileBytes++; // skip starting quote
+ while (true)
+ {
+ c = *pFileBytes++; // get char and advance
+
+ if (!c) // EOL
+ {
+ pToken[len] = '\0';
+ return pFileBytes - 1; // we are done with that, but return data to show that token is present
+ }
+
+ if (c == '\"') // closing quote
+ {
+ pToken[len] = '\0';
+ return pFileBytes;
+ }
+
+ pToken[len++] = c;
+ }
+ }
+
+ // parse single characters
+ if (IN_CHARACTERSET(breaks, c))
+ {
+ pToken[len++] = c;
+ pToken[len] = '\0';
+ return pFileBytes + 1;
+ }
+
+ // parse a regular word
+ do
+ {
+ pToken[len++] = c;
+ pFileBytes++;
+
+ c = *pFileBytes;
+ if (IN_CHARACTERSET(breaks, c))
+ break;
+ }
+ while (c > 32);
+
+ pToken[len] = '\0';
+ return pFileBytes;
+}
+
+char *ParseFile(char *pFileBytes, char *pToken, bool *pWasQuoted)
+{
+ return const_cast(ParseFile(static_cast(pFileBytes), pToken, pWasQuoted));
+}
+
+void NORETURN FileSystem_SysError(const char *fmt, ...)
+{
+ va_list argptr;
+ static char string[8192];
+
+ va_start(argptr, fmt);
+ vsnprintf(string, sizeof(string), fmt, argptr);
+ va_end(argptr);
+
+ printf("%s\n", string);
+
+ FILE* fl = fopen("filesystem_error.txt", "w");
+ fprintf(fl, "%s\n", string);
+ fclose(fl);
+
+ int *null = 0;
+ *null = 0;
+ exit(-1);
+}
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.h b/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.h
new file mode 100644
index 0000000..f073acd
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/filesystem_helpers.h
@@ -0,0 +1,36 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#include "tier0/characterset.h"
+
+// Call until it returns nullptr. Each time you call it, it will parse out a token.
+struct characterset_t;
+
+const char *ParseFile(const char *pFileBytes, char *pToken, bool *pWasQuoted, characterset_t *pCharSet = nullptr);
+char *ParseFile(char *pFileBytes, char *pToken, bool *pWasQuoted); // (same exact thing as the const version)
+void NORETURN FileSystem_SysError(const char *fmt, ...);
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/linux_support.cpp b/rehlds/filesystem/FileSystem_Stdio/src/linux_support.cpp
new file mode 100644
index 0000000..fba110d
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/linux_support.cpp
@@ -0,0 +1,258 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#include "precompiled.h"
+
+#if !defined(_WIN32)
+
+char selectBuf[PATH_MAX];
+int FileSelect(const struct dirent *ent)
+{
+ const char *mask = selectBuf;
+ const char *name = ent->d_name;
+
+ //printf("Test:%s %s\n", mask, name);
+
+ if (!Q_strcmp(name, ".") || !Q_strcmp(name, ".."))
+ return 0;
+
+ if (!Q_strcmp(selectBuf, "*.*"))
+ return 1;
+
+ while (*mask && *name)
+ {
+ if (*mask == '*')
+ {
+ mask++; // move to the next char in the mask
+ if (!*mask) // if this is the end of the mask its a match
+ {
+ return 1;
+ }
+ while (*name && toupper(*name) != toupper(*mask))
+ {
+ // while the two don't meet up again
+ name++;
+ }
+ if (!*name)
+ {
+ // end of the name
+ break;
+ }
+ }
+ else if (*mask != '?')
+ {
+ if (toupper(*mask) != toupper(*name))
+ {
+ // mismatched!
+ return 0;
+ }
+ else
+ {
+ mask++;
+ name++;
+ if (!*mask && !*name)
+ {
+ // if its at the end of the buffer
+ return 1;
+ }
+
+ }
+
+ }
+ // mask is "?", we don't care
+ else
+ {
+ mask++;
+ name++;
+ }
+ }
+
+ // both of the strings are at the end
+ return (!*mask && !*name);
+}
+
+int FillDataStruct(FIND_DATA *dat)
+{
+ char szFileName[MAX_PATH];
+ struct stat fileStat;
+
+ if (dat->numMatches < 0)
+ return -1;
+
+ Q_strlcpy(szFileName, dat->cDir);
+ Q_strlcat(szFileName, "/");
+ Q_strlcat(szFileName, dat->namelist[dat->numMatches]->d_name);
+ Q_strlcpy(dat->cFileName, dat->namelist[dat->numMatches]->d_name);
+
+ if (!stat(szFileName, &fileStat))
+ {
+ dat->dwFileAttributes = fileStat.st_mode;
+ }
+ else
+ {
+ dat->dwFileAttributes = 0;
+ }
+
+ // printf("%s\n", dat->namelist[dat->numMatches]->d_name);
+ free(dat->namelist[dat->numMatches]);
+ dat->numMatches--;
+ return 1;
+}
+
+int FindFirstFile(char *fileName, FIND_DATA *dat)
+{
+ char nameStore[PATH_MAX];
+ char *dir = nullptr;
+ int n, iret = -1;
+
+ Q_strlcpy(nameStore, fileName);
+
+ if (Q_strrchr(nameStore, '/'))
+ {
+ dir = nameStore;
+ while (Q_strrchr(dir, '/'))
+ {
+ struct stat dirChk;
+
+ // zero this with the dir name
+ dir = Q_strrchr(nameStore, '/');
+ *dir = '\0';
+
+ dir = nameStore;
+ stat(dir, &dirChk);
+
+ if (S_ISDIR(dirChk.st_mode))
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ // couldn't find a dir seperator...
+ return -1;
+ }
+
+ if (Q_strlen(dir) > 0)
+ {
+ Q_strlcpy(selectBuf, fileName + Q_strlen(dir) + 1);
+ Q_strlcpy(dat->cDir, dir);
+
+ n = scandir(dir, &dat->namelist, FileSelect, alphasort);
+ if (n < 0)
+ {
+ // silently return, nothing interesting
+ printf("scandir failed:%s\n", dir);
+ }
+ else
+ {
+ // n is the number of matches
+ dat->numMatches = n - 1;
+ iret = FillDataStruct(dat);
+ if (iret < 0)
+ {
+ free(dat->namelist);
+ }
+ }
+ }
+
+ // printf("Returning: %i \n", iret);
+ return iret;
+}
+
+bool FindNextFile(int handle, FIND_DATA *dat)
+{
+ if (dat->numMatches < 0)
+ {
+ free(dat->namelist);
+ return false; // no matches left
+ }
+
+ FillDataStruct(dat);
+ return true;
+}
+
+bool FindClose(int handle)
+{
+ return true;
+}
+
+static char fileName[MAX_PATH];
+int CheckName(const struct dirent *dir)
+{
+ return !Q_stricmp(dir->d_name, fileName);
+}
+
+const char *findFileInDirCaseInsensitive(const char *file)
+{
+ const char *dirSep = Q_strrchr(file, '/');
+ if (!dirSep)
+ {
+ dirSep = Q_strrchr(file, '\\');
+ if (!dirSep)
+ {
+ return nullptr;
+ }
+ }
+
+ char *dirName = static_cast(alloca(((dirSep - file) + 1) * sizeof(char)));
+ if (!dirName) {
+ return nullptr;
+ }
+
+ Q_strncpy(dirName, file, dirSep - file);
+ dirName[dirSep - file] = '\0';
+
+ struct dirent **namelist;
+ int n;
+
+ Q_strlcpy(fileName, dirSep + 1);
+ n = scandir(dirName, &namelist, CheckName, alphasort);
+
+ if (n > 0)
+ {
+ while (n > 1)
+ {
+ // free the malloc'd strings
+ free(namelist[n]);
+ n--;
+ }
+
+ Q_snprintf(fileName, sizeof(fileName), "%s/%s", dirName, namelist[0]->d_name);
+ free(namelist[0]);
+ return fileName;
+ }
+ else
+ {
+ Q_strlcpy(fileName, file);
+ Q_strlwr(fileName);
+ return fileName;
+ }
+}
+
+#endif // #if !defined(_WIN32)
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/linux_support.h b/rehlds/filesystem/FileSystem_Stdio/src/linux_support.h
new file mode 100644
index 0000000..96daef9
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/linux_support.h
@@ -0,0 +1,62 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+*/
+
+#pragma once
+
+#ifndef _WIN32
+
+#include // tolower()
+#include // PATH_MAX define
+#include // strcmp, strcpy
+#include // stat()
+#include
+#include // scandir()
+#include
+#include
+
+#define FILE_ATTRIBUTE_DIRECTORY S_IFDIR
+
+typedef struct
+{
+ // public data
+ int dwFileAttributes;
+ char cFileName[MAX_PATH]; // the file name returned from the call
+
+ int numMatches;
+ struct dirent **namelist;
+ char cDir[MAX_PATH];
+} FIND_DATA;
+
+#define WIN32_FIND_DATA FIND_DATA
+
+int FindFirstFile(char *findName, FIND_DATA *dat);
+bool FindNextFile(int handle, FIND_DATA *dat);
+bool FindClose(int handle);
+const char *findFileInDirCaseInsensitive(const char *file);
+
+#endif // _WIN32
diff --git a/rehlds/filesystem/FileSystem_Stdio/src/pathmatch.cpp b/rehlds/filesystem/FileSystem_Stdio/src/pathmatch.cpp
new file mode 100644
index 0000000..9d4f722
--- /dev/null
+++ b/rehlds/filesystem/FileSystem_Stdio/src/pathmatch.cpp
@@ -0,0 +1,898 @@
+/*
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation; either version 2 of the License, or (at
+* your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+* In addition, as a special exception, the author gives permission to
+* link the code of this program with the Half-Life Game Engine ("HL
+* Engine") and Modified Game Libraries ("MODs") developed by Valve,
+* L.L.C ("Valve"). You must obey the GNU General Public License in all
+* respects for all of the code used other than the HL Engine and MODs
+* from Valve. If you modify this file, you may extend this exception
+* to your version of the file, but you are not obligated to do so. If
+* you do not wish to do so, delete this exception statement from your
+* version.
+*
+* ------------------------------------------------------------------------------------
+* This file implements the --wrap for ld on linux that lets file i/o api's
+* behave as if it were running on a case insensitive file system. Unfortunately,
+* this is needed by both steam2 and steam3. It was decided to check the source
+* into both locations, otherwise someone would find the .o and have no idea
+* where to go for the source if it was in the 'other' tree. Also, because this
+* needs to be linked into every elf binary, the .o is checked in for Steam3 so that it is
+* always available. In Steam2 it sits with the PosixWin32.cpp implementation and gets
+* compiled along side of it through the make system. If you are reading this in Steam3,
+* you will probably want to actually make your changes in steam2 and do a baseless merge
+* to the steam3 copy.
+*
+* HOWTO: Add a new function. Add the function with _WRAP to the makefiles as noted below.
+* Add the implementation to pathmatch.cpp - probably mimicking the existing functions.
+* Build steam2 and copy to matching steam3/client. Take the pathmatch.o from steam 2
+* and check it in to steam3 (in the location noted below). Full rebuild (re-link really)
+* of steam3. Test steam and check in.
+*
+* If you are looking at updating this file, please update the following as needed:
+*
+* STEAM2.../Projects/GazelleProto/Client/Engine/obj/RELEASE_NORMAL/libsteam_linux/Common/Misc/pathmatch.o
+* This is where steam2 builds the pathmatch.o out to.
+*
+* STEAM2.../Projects/GazelleProto/Makefile.shlib.base - contains _WRAP references
+* STEAM2.../Projects/Common/Misc/pathmatch.cpp - Where the source is checked in, keep in sync with:
+* STEAM3.../src/common/pathmatch.cpp - should be identical to previous file, but discoverable in steam3.
+* STEAM3.../src/lib/linux32/release/pathmatch.o - steam3 checked in version
+* STEAM3.../src/devtools/makefile_base_posix.mak - look for the _WRAP references
+* ------------------------------------------------------------------------------------
+*/
+
+#include "precompiled.h"
+
+#ifndef _WIN32
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Enable to do pathmatch caching. Beware: this code isn't threadsafe.
+// #define DO_PATHMATCH_CACHE
+
+#ifdef DO_PATHMATCH_CACHE
+#include