IFileSystemV009
The SteamPipe update and subsequent Half-Life SDK release on Github came with a modified version of the IFileSystem interface that is designed to work with GoldSource's file searching procedures.
It provides a means to use the search paths defined by the engine to find files in the same way that the engine does, as well as allowing new search paths to be added to find content in other directories.
See IFileSystem for information on operations shared between the GoldSource and Source engine versions of the interface.
Path IDs
In GoldSource, the same file can exist in multiple SearchPaths. IFileSystem
defines a few different access modes for determining precisely where it should look:
ROOT
- The root directory. This is the location of the game's executable.
BASE
- Same as ROOT.
DEFAULT
- Default paths. Generally used when searching all paths.
PLATFORM
- The game directory's platform directory.
GAME
- Game directories, including localization, _addon and _hd.
GAMECONFIG
- The directory containing game configuration files. This is the mod directory.
GAMEDOWNLOAD
- The _downloads directory for the mod.
GAME_FALLBACK
- If the
fallback_dir
option is provided inliblist.gam
, this path ID is also set. Essentially, this enables you to easily specify that another game's content should also be mounted. It will add search paths for its_addon
,_hd
and localization directories as well. For example, Condition Zero defines its fallback directory to be "cstrike".
SteamPipe directories
The SteamPipe update also introduced a new set of directories that content is to be placed in. These are intended to prevent pollution of default content by custom content, and to prevent pollution of default and manually installed custom content by automatically downloaded content.
Content located in one of these directories will override the content located in the <base dir>
and mod directories.
The following directories now exist:
<base dir>
- The base directory is where all Half-Life content is normally loaded from. It defaults to
valve/
, but can be overridden by using the-basedir
command line parameter. <mod dir>
- As before, the mod directory provides default content, the server and client libraries, and configuration files.
<mod dir>_addon
- If the
-addons
command line parameter has been specified, this directory will be added as aGAME
search path. <mod dir>_downloads
- This directory is where all content downloaded from game servers is stored at. Unlike
_addon
, this directory is not added to theGAME
path ID. Presumably this is to prevent downloaded content from overriding default content in sensitive situations, like when server configurations are being executed. <mod dir>_hd
- If HD models are enabled, this directory is also added as a search path. It is intended to provide HD models that override default versions.
Instantiating an IFileSystem
There is only one instance of the IFileSystem interface, located in the filesystem_stdio
library (filesystem_steam
is obsolete).
Getting the instance takes a few steps (code modified from DMC's source):
#include "interface.h"
#include "FileSystem.h"
CSysModule* g_pFileSystemModule = NULL;
IFileSystem* g_pFileSystem = NULL;
bool LoadFileSystem()
{
// Determine which filesystem to use.
#if defined ( _WIN32 )
char *szFsModule = "filesystem_stdio.dll";
#elif defined(OSX)
char *szFsModule = "filesystem_stdio.dylib";
#elif defined(LINUX)
char *szFsModule = "filesystem_stdio.so";
#else
#error
#endif
char szFSDir[ MAX_PATH ];
szFSDir[ 0 ] = 0;
#ifdef CLIENT_DLL
if( gEngfuncs.COM_ExpandFilename( szFsModule, szFSDir, sizeof( szFSDir ) ) == FALSE )
{
return false;
}
#else
//Just use the filename for the server. No COM_ExpandFilename here.
strcpy( szFSDir, szFsModule );
#endif
// Get filesystem interface.
g_pFileSystemModule = Sys_LoadModule( szFSDir );
assert( g_pFileSystemModule );
if( !g_pFileSystemModule )
{
return false;
}
CreateInterfaceFn fileSystemFactory = Sys_GetFactory( g_pFileSystemModule );
if( !fileSystemFactory )
{
return false;
}
g_pFileSystem = ( IFileSystem* ) fileSystemFactory( FILESYSTEM_INTERFACE_VERSION, nullptr );
assert( g_pFileSystem );
if( !g_pFileSystem )
{
return false;
}
return true;
}
Let's go over each step:
#include "interface.h"
#include "FileSystem.h"
In order to make use of the interface, its definition must be included. FileSystem.h
includes that definition, and uses interface.h
.
CSysModule* g_pFileSystemModule = NULL;
IFileSystem* g_pFileSystem = NULL;
These global variables will keep track of the filesystem module and the filesystem itself, respectively.
// Determine which filesystem to use.
#if defined ( _WIN32 )
char *szFsModule = "filesystem_stdio.dll";
#elif defined(OSX)
char *szFsModule = "filesystem_stdio.dylib";
#elif defined(LINUX)
char *szFsModule = "filesystem_stdio.so";
#else
#error
#endif
Based on the platform that you're compiling the game for, you'll need a specific library name and extension. The pointer szFsModule
will point to the correct name.
char szFSDir[ MAX_PATH ];
szFSDir[ 0 ] = 0;
#ifdef CLIENT_DLL
if( gEngfuncs.COM_ExpandFilename( szFsModule, szFSDir, sizeof( szFSDir ) ) == FALSE )
{
return false;
}
#else
//Just use the filename for the server. No COM_ExpandFilename here.
strcpy( szFSDir, szFsModule );
#endif
The full name of the library file should now be determined. On the client side, you can use COM_ExpandFilename to get the absolute path of the file. The server does not expose this function (which actually accesses the filesystem itself to get this information), so you'll have to rely on the game using the correct working directory while loading it.
// Get filesystem interface.
g_pFileSystemModule = Sys_LoadModule( szFSDir );
assert( g_pFileSystemModule );
if( !g_pFileSystemModule )
{
return false;
}
Next we load the filesystem library. The library will already have been loaded and initialized by the engine, so we're really just getting a handle to the library.
If this fails, we stop and return false to indicate failure.
CreateInterfaceFn fileSystemFactory = Sys_GetFactory( g_pFileSystemModule );
if( !fileSystemFactory )
{
return false;
}
In order to instantiate the filesystem, we need to have the library's CreateInterface function.
If we fail to retrieve it, we stop and return false to indicate failure.
g_pFileSystem = ( IFileSystem* ) fileSystemFactory( FILESYSTEM_INTERFACE_VERSION, nullptr );
assert( g_pFileSystem );
if( !g_pFileSystem )
{
return false;
}
Now we instantiate the filesystem by querying the CreateInterface function for the interface version.
If this fails, it can only mean that the filesystem isn't exposed for that version. In practice this should never happen, unless a breaking change is made to the interface that requires all users to upgrade, or if the library has been replaced by a third party.
return true;
If we've reached this point, that means we've successfully instantiated the filesystem. We can now use it for any file operations, add new search paths, etc.
The LoadFileSystem
function should be called in the Initialize
function in the client library and either the GiveFnptrsToDll
or GameDLLInit
functions in the server library. Note that using GameDLLInit will require you to submit a "quit" server command in order to shut down the server in the event that the filesystem fails to load.
Notes
This version of the filesystem was originally designed for Steam (GCF) based content. As such, its interface contains methods to perform Steam specific actions. Since the SteamPipe update, these will no longer do anything.
The RemoveSearchPath
method will cause an access violation regardless of which path you give it. This bug has been reported on Half-Life's bug tracker [1].