Difference between revisions of "IFileSystem"

From Valve Developer Community
Jump to: navigation, search
(Removed "localhost" Doxygen link)
(Added note referencing IFileSystemV009)
 
(18 intermediate revisions by 10 users not shown)
Line 1: Line 1:
Here you will find all the information you ever wanted to know about the '''File System''' in [[Source]]: how to open files, how to read files, how to create new directories and how to find a file on the local system.
+
{{toc-right}}
  
== Include ==
+
Source replaces the standard file handling functions (<code>fopen</code> etc.) with its own. The functions, members of the global '''<code>IFileSystem* filesystem</code>''' object, provide access to the whole engine filesystem, including cascading access through all mounted [[Gameinfo.txt#SearchPaths|SearchPaths]] and access within [[GCF]] files.
#include "Filesystem.h"
 
  
==My Game Directory==
+
You must <code>#include "Filesystem.h"</code> before you can use it.
  
This does not deal specifically with the filesystem, but you might find this useful for your project.
+
{{tip|If you really do want to use <code>fopen</code>, just do <code>#undef fopen</code>.}}
  
//Server
+
{{note|GoldSource uses a different version of this interface. See [[IFileSystemV009]] for more information.}}
char * pGameDir = new char[1024];
 
engine->GetGameDir(pGameDir, 1024);
 
 
//Client
 
const char *pGameDir = engine->GetGameDirectory();
 
  
==Create Directory==
+
== CBaseFile ==
  
To create a directory you need to know two things; the '''relative path''' (Can include folders, i.e. scripts/weapon.txt) containing the new directory name and '''Path ID''' where the path will be created &mdash; called a Path ID because it does not appear to be possible to create a directory using anything other than a search path id.
+
[[Tier2]] provides <code>CBaseFile</code> and various derivatives, which operate like the C++ <code>[[w:fstream|fstream]]</code> family. <code>#include "tier2\fileutils.h"</code> to access them.
  
===Default Path Ids===
+
== Paths IDs ==
  
* <code>DEFAULT_WRITE_PATH</code>
+
In Source, the same file can exist in multiple SearchPaths. <code>IFileSystem</code> defines a few different access modes for determining precisely where it should look:
* <code>MOD</code>
 
* <code>GAME</code>
 
* <code>GAMEBIN</code>
 
  
char * relpath = new char[1024];
+
; <code>MOD</code>
V_snprintf(relpath, 1024, "stuff\\%s", "newdirectory");
+
: The first SearchPath only.
filesystem->CreateDirHierarchy( relpath, "MOD" );
+
; <code>GAME</code>
 +
: All SearchPaths, including those inside GCFs.
 +
; <code>XGAME</code>
 +
: Xbox 360 equivalent to <code>GAME</code>.
 +
; <code>GAMEBIN</code>
 +
: The game binaries folder (client, server).
 +
; <code>EXECUTABLE_PATH</code>
 +
: The engine binaries folder.
 +
; <code>DEFAULT_WRITE_PATH</code>
 +
: Wherever the game is currently configured to write out to. Defaults to the first SearchPath.
  
This code will create a <code>newdirectory</code> in your <code>stuff</code> directory, which if does not exist will be created along with <code>newdirectory</code> inside of your MOD directory.
+
You can get the path of the gameinfo.txt folder like this:
  
==Delete Directory==
+
<source lang=cpp>
To remove a directory, first make sure to remove all the files in that directory.  Then you can call this code:
+
// server
 +
char pGameDir[MAX_PATH];
 +
engine->GetGameDir(pGameDir, MAX_PATH);
  
#include "direct.h"
+
// client
 +
const char *pGameDir = engine->GetGameDirectory();
 +
</source>
  
char *toRemove = new char[1096];
+
== Fixing paths ==
toRemove[0] = 0;
 
#ifndef CLIENT_DLL
 
  char * pGameDir = new char[1024];
 
  engine->GetGameDir(pGameDir, 1024);
 
#else
 
  const char *pGameDir = engine->GetGameDirectory();
 
#endif
 
V_strcat(toRemove, pGameDir);
 
//concatenate the rest of the directory name here
 
_rmdir(toRemove);
 
  
==Copy File==
+
<source lang=cpp>
{{warning|Untested}}
+
filename = "materials\metal\metalcombine001";
bool CopyFile(const char *from, const char *to)
+
V_SetExtension( filename, ".vmt", sizeof(filename) );
{
+
V_FixSlashes(filename);
Assert(from);
+
</source>
Assert(to);
 
 
CUtlBuffer buf;
 
 
// Read in file to copy
 
if(filesystem->ReadFile(from, "MOD", buf))
 
return filesystem->WriteFile(to, "MOD", buf); // Write out copy
 
 
return false;
 
}
 
  
==File I/O==
+
These functions are self-explanatory. '''Always call <code>V_FixSlashes()</code>''', as slash direction differs between Windows and Linux/Mac!
  
===I/O Modes===
+
== File ==
{|
+
 
| <code>r</code> || Open a text file for reading
+
=== Modes ===
|-
+
 
| <code>w</code> || Create a text file for writing
+
These values are passed as string literals when opening files.
|-
+
 
| <code>a</code> || Append to a text file
+
{| class=standard-table
|-
+
| <code>r</code> || Open for reading
| <code>rb</code> || Open a binary file for reading
 
|-
 
| <code>wb</code> || Create a binary file for writing
 
|-
 
| <code>ab</code> || Append to a binary file
 
 
|-
 
|-
| <code>r+</code> || Open a text file for read/write
+
| <code>w</code> || Create for writing (will overwrite existing)
 
|-
 
|-
| <code>w+</code> || Create a text file for read/write
+
| <code>a</code> || Append to
 
|-
 
|-
| <code>a+</code> || Append to or create a text file for read/write
+
| <code>r+</code> || Open for read/write
 
|-
 
|-
| <code>rb+</code> || Open a binary file for read/write
+
| <code>w+</code> || Create for read/write
 
|-
 
|-
| <code>wb+</code> || Create a binary file for read/write
+
| <code>a+</code> || Append to or create for read/write
 
|-
 
|-
| <code>ab+</code> || Append to or create a binary file for read/write
+
| <code>b</code> || When used with any of the above, flags file as binary
 
|-
 
|-
 
|}
 
|}
  
FileHandle_t fh;
+
=== Reading ===
fh = filesystem->Open( "mylog.log", "a", "MOD");
+
 
+
<source lang=cpp>
if (fh)
+
#include "Filesystem.h"
{
+
 
    filesystem->FPrintf(fh, "%s", "Logging A Test Line...");   
+
FileHandle_t fh = filesystem->Open("gameinfo.txt","r","GAME");
    filesystem->Close(fh);
+
 
}
+
if(fh)
 +
{
 +
int file_len = filesystem->Size(fh);
 +
char* GameInfo = new char[file_len + 1];
 +
 
 +
filesystem->Read((void*)GameInfo,file_len,fh);
 +
GameInfo[file_len] = 0; // null terminator
 +
 
 +
filesystem->Close(fh);
 +
 
 +
// Use GameInfo here...
 +
 
 +
delete[] GameInfo;
 +
}
 +
</source>
 +
 
 +
This code opens gameinfo.txt in read-only mode, then stores its contents in <code>GameInfo</code>, a [[W:C string|C string]]. Because the string was created with the <code>[[W:new (C++)|new]]</code> keyword, it is very important to call <code>[[W:delete (C++)|delete[]]]</code> on it afterwards or the memory would be leaked. (If you know in advance the size of the file, you can do <code>char MyString[the_length]</code> as normal, and not risk any leaks.)
 +
 
 +
There is also a helper function that handles open/read/close for you in one swoop:
 +
 
 +
<source lang=cpp>
 +
#include "Filesystem.h"
 +
#include "utlbuffer.h"
 +
 
 +
CUtlBuffer buf;
 +
if ( filesystem->ReadFile("gameinfo.txt","GAME",buf) )
 +
{
 +
char* GameInfo = new char[buf.Size() + 1];
 +
buf.GetString(GameInfo);
 +
GameInfo[buf.Size()] = 0; // null terminator
 +
 
 +
// Use GameInfo here...
 +
 
 +
delete[] GameInfo;
 +
}
 +
</source>
 +
 
 +
{{todo|Is there a better way of reading from a [[CUtlBuffer]]?}}
 +
 
 +
=== Writing ===
 +
 
 +
<source lang=cpp>
 +
FileHandle_t fh = filesystem->Open( "mylog.log", "a+", "MOD");
 +
 
 +
if (fh)
 +
{
 +
filesystem->FPrintf(fh, "%s", "Logging A test line...");
 +
 
 +
char* text = "Logging another test line...";
 +
filesystem->Write(text, V_strlen(text), fh);   
 +
 
 +
filesystem->Close(fh);
 +
}
 +
</source>
 +
 
 +
There is also <code>filesystem->WriteFile(char* name,char* path,CUtlBuffer &buf)</code>.
 +
 
 +
=== Searching ===
 +
 
 +
<source lang=cpp>
 +
FileFindHandle_t findHandle; // note: FileFINDHandle
 +
 
 +
const char *pFilename = filesystem->FindFirstEx( "*.*", "MOD", &findHandle );
 +
while (pFilename)
 +
{
 +
Msg("%s\n",pFilename);
 +
pFilename = filesystem->FindNext( findHandle );
 +
}
 +
 
 +
filesystem->FindClose( findHandle );
 +
</source>
  
or
+
This code lists all files and folders in the gameinfo.txt folder. The <code>Find</code> functions do not search subfolders, and return only the filename matched, not its entire path.
  
FileHandle_t fh;
+
To iterate over subfolders, use <code>filesystem->FindIsDirectory()</code> and alter the search terms (e.g. <code>materials\\*.*</code>).
fh = filesystem->Open( "mylog.log", "a", "MOD");
 
 
if (fh)
 
{
 
    char* text = "Logging A Test Line...";
 
    filesystem->Write(text, V_strlen(text), fh); 
 
    filesystem->Close(fh);
 
}
 
  
So in this example we open <code>mylog.log</code> in '''Append''' mode.  Then we print the line <code>Logging A Test Line...</code> and close the file.
+
== Directories ==
  
FileHandle_t File; //file wer're reading
+
=== Creating ===
File = filesystem->Open( "test/test.txt", "r", "MOD"); //opens a pre-made file in the MOD/test directory called test.txt
 
int size = filesystem->Size(File); //gets the size of the file
 
char *Raw = new char[ size + 1 ]; //makes a pointer array 1 bigger to sign
 
filesystem->Read( Raw, size, File); // reads into the pointer array, the size of the file charecters, from the file
 
Raw[size] = 0; //signs
 
Msg(Raw); //outputs to the command line
 
  
Toss this little bit of code into a function called by a ConCommand and it can spit out the contents of a file.
+
<source lang=cpp>
 +
char* path = "test_dir/subdir";
 +
V_FixSlashes(path);
 +
filesystem->CreateDirHierarchy(path,"DEFAULT_WRITE_PATH");
 +
</source>
  
==Search Files==
+
=== Removing ===
To iterate through a list of files in a given '''Path ID''' you would do the following.
 
  
FileFindHandle_t findHandle;
+
<code>IFileSystem</code> does not include support for deleting directories, and neither does the C++ standard. You'll need to either not delete them, or fall back on platform-specific APIs.
const char *pFilename = filesystem->FindFirstEx( "*.*", "MOD", &findHandle );
 
while ( pFilename != NULL )
 
{
 
    pFilename = filesystem->FindNext( findHandle );
 
}
 
filesystem->FindClose( findHandle );
 
  
<code>*.*</code> is the wildcard to match the files against.  <code>"MOD"</code> is the '''Path ID'''.
+
One function you will find useful when doing this is <code>filesystem->RelativePathToFullPath</code>.
  
The value in <code>pFilename</code> for each <code>filesystem->FindNext</code> is nothing but the file name (i.e. for <code>maps/d1_trainstation_01.bsp</code>, <code>pFilename</code> would be <code>d1_trainstation_01</code>)
+
== Mounting [[:Category:Steam Applications|Steam Application]] Content ==
  
==Mount [[:Category:Steam Applications|Steam Application]] Content==
 
 
The following code segment should be added to both <code>CHLClient::Init</code> in src\cl_dll\cdll_client_int.cpp and <code>CServerGameDLL::DLLInit</code> in src\dlls\GameInterface.cpp after <code>filesystem</code> is initialized.
 
The following code segment should be added to both <code>CHLClient::Init</code> in src\cl_dll\cdll_client_int.cpp and <code>CServerGameDLL::DLLInit</code> in src\dlls\GameInterface.cpp after <code>filesystem</code> is initialized.
 
  filesystem->AddSearchPath("'''path'''", "GAME");
 
  filesystem->AddSearchPath("'''path'''", "GAME");
Line 153: Line 184:
  
 
'''<tt>-</tt>''' is a negative sign (Subtract) and is required to mount content properly.
 
'''<tt>-</tt>''' is a negative sign (Subtract) and is required to mount content properly.
 +
 +
==Interface Details==
 +
The IFileSystem interface provides routines for accessing content on both the local drives, and in packages.
 +
 +
===Search Paths===
 +
File paths are usually formatted as a relative path to the current folder. For example:
 +
 +
.\materials\Brick\brickfloor001a.vtf
 +
 +
Content from several sources is merged into this common root folder using search paths. This provides useful, however complex to the uninitiated, behaviour for accessing content. While the file used in the example above originates from the <code>source 2007 shared material.gcf</code>, not all files found in the <code>.\materials</code> folder will originate from the same place. Another example:
 +
 +
.\materials\myCustomMadeTexture.vtf
 +
 +
This file would most likely reside on the hard disk, in a mod folder, under its materials directory. The full path may be something like this:
 +
 +
C:\Program Files\Steam\SteamApps\SourceMods\MyMod\Materials\myCustomMadeTexture.vtf
 +
 +
The IFileSystem function GetSearchPaths retrieves a list of the search paths used to achieve this behaviour. Typical paths that would be returned:
 +
 +
C:\program files\steam\steamapps\sourcemods\mymod
 +
C:\program files\steam\steamapps\''username''\sourcesdk\bin\orangebox\hl2
 +
 +
When the calling application attempts to open <code>.\materials\myCustomMadeTexture.vtf</code>, this first search path allows it to be interpreted as the full path. There may be many more search paths than these two. Whichever search path first finds an existing file, is the search path that is used. Hence, the order of the search paths is significant, and the GetSearchPaths function returns them in the correct order.
 +
 +
While this makes clear how hard disk files are found, the matter of mounted content requires further explanation.
 +
 +
===Mounted Content===
 +
The second search path example above is what allows mounted content to be found. If one examines this folder on their hard disk, they will find little there. However, the steam file system can mount content (typically based on AppId) as paths in the local file system. When the IFileSystem is created, the content is mounted to the ''current directory'' of the calling code. Note, however that this directory should NOT be <code>...\orangebox\hl2</code>. This is because the ''root'' of the associated packs are mounted to the current directory. GCFs such as the <code>source 2007 shared material.gcf</code> contain a folder called hl2 inside their root. When the GCF is mounted, this hl2 folder inside the gcf becomes <code>...orangebox\hl2</code>. Therefore, when the IFileSystem is Loaded/Connected, the current directory should first be changed to:
 +
 +
C:\program files\steam\steamapps\''username''\sourcesdk\bin\orangebox
 +
 +
Or the appropriate path for the version of the source SDK being used.
 +
 +
===Instantiating an IFileSystem===
 +
Though the IFileSystem interface cannot itself be instantiated, the closed source class that implements it can be, which provides us with a working IFileSystem. Several steps must be taken to retrieve an IFileSystem that functions as expected (functions at all).
 +
 +
Information that must be known:
 +
* Whether steam is running or not
 +
* The game directory (for example: c:\program files\steam\steamapps\sourcemods\mymod)
 +
* the SDK base directory (C:\program files\steam\steamapps\''username''\sourcesdk\bin\orangebox)
 +
* Whether or not to mount ExtraAppId
 +
 +
=====Is Steam Running?=====
 +
The easiest way to do this is to check for steam.exe as a running process. However, this is not the safest way to allow the application to proceed. (For more details, see [http://ducttape.cvs.sourceforge.net/viewvc/ducttape/DuctTape/SteamFS/SteamFS/API.cpp?revision=1.3&view=markup Api.cpp] API::FileSystemReady in the DuctTape project). If Steam is not ready to provide the file system interface, the calling application will terminate immediately with a message printed to the debug output: "steam is not running"
 +
 +
=====Preparing the Runtime Environment=====
 +
* The location of <code>steam.dll</code> must be in the <code>path</code> environment variable.
 +
* If steam is an NT service (running in Vista) then the SteamAppUser environment variable must be set to the users name. This can be retrieved from the AutoLoginUser key in file:
 +
 +
''steam install path''\config\SteamAppData.vdf
 +
 +
* The SteamAppId environment variable must be set to the AppId found in the mods/games gameinfo.txt
 +
* Set the sourcesdk environment variable to the SDK base directory
 +
* Change the current directory to the SDK base directory
 +
 +
=====Loading the FileSystem Module=====
 +
The full path of filesystem_steam.dll must be determined, which resides in the source sdk's bin directory (orangebox\bin). This file must be used from this location, or mounted content will not be retrievable.
 +
 +
* Call the Sys_LoadInterface function. It returns true on success:
 +
 +
CSysModule** module;
 +
IFileSystem* fileSystem;
 +
Sys_LoadInterface(''fullPathToFileSystemDLL'', "VFileSystem017", module, (void**)&fileSystem);
 +
 +
* Connect the file system. It returns true on success:
 +
 +
fileSystem->Connect(Sys_GetFactoryThis());
 +
 +
* Initialize the files system. It returns INIT_OK on success.
 +
 +
fileSystem->Init();
 +
 +
* At this point, current directory can be restored.
 +
 +
=====Mount the extra content=====
 +
If SDK extra content is to be mounted, the ToolsAppID must be retrieved from gameinfo.txt. Currently it is 211.
 +
 +
fileSystem->MountSteamContent(toolsAppId);
 +
 +
=====Load Search Paths=====
 +
The search paths must be loaded from gameinfo.txt. For a thorough example of how this is to be done, see [http://ducttape.cvs.sourceforge.net/viewvc/ducttape/DuctTape/SteamFS/SteamFS/filesystem_init.cpp?revision=1.10&view=markup filesystem_init.cpp] FileSystem_LoadSearchPaths in the DuctTape project. A simple explanation of how search paths are loaded can be found in the unmodified gameinfo.txt of a new mod.
  
 
==See Also==
 
==See Also==
 
* [[Mounting Other Content]]
 
* [[Mounting Other Content]]
[[Category:Programming]][[Category:Interfaces]]
+
 
 +
[[Category:Interfaces|F]]

Latest revision as of 18:59, 2 July 2016

Source replaces the standard file handling functions (fopen etc.) with its own. The functions, members of the global IFileSystem* filesystem object, provide access to the whole engine filesystem, including cascading access through all mounted SearchPaths and access within GCF files.

You must #include "Filesystem.h" before you can use it.

Tip:If you really do want to use fopen, just do #undef fopen.
Note:GoldSource uses a different version of this interface. See IFileSystemV009 for more information.

CBaseFile

Tier2 provides CBaseFile and various derivatives, which operate like the C++ fstream family. #include "tier2\fileutils.h" to access them.

Paths IDs

In Source, the same file can exist in multiple SearchPaths. IFileSystem defines a few different access modes for determining precisely where it should look:

MOD
The first SearchPath only.
GAME
All SearchPaths, including those inside GCFs.
XGAME
Xbox 360 equivalent to GAME.
GAMEBIN
The game binaries folder (client, server).
EXECUTABLE_PATH
The engine binaries folder.
DEFAULT_WRITE_PATH
Wherever the game is currently configured to write out to. Defaults to the first SearchPath.

You can get the path of the gameinfo.txt folder like this:

// server
char pGameDir[MAX_PATH];
engine->GetGameDir(pGameDir, MAX_PATH);

// client
const char *pGameDir = engine->GetGameDirectory();

Fixing paths

filename = "materials\metal\metalcombine001";
V_SetExtension( filename, ".vmt", sizeof(filename) );
V_FixSlashes(filename);

These functions are self-explanatory. Always call V_FixSlashes(), as slash direction differs between Windows and Linux/Mac!

File

Modes

These values are passed as string literals when opening files.

r Open for reading
w Create for writing (will overwrite existing)
a Append to
r+ Open for read/write
w+ Create for read/write
a+ Append to or create for read/write
b When used with any of the above, flags file as binary

Reading

#include "Filesystem.h"

FileHandle_t fh = filesystem->Open("gameinfo.txt","r","GAME");

if(fh)
{
	int file_len = filesystem->Size(fh);
	char* GameInfo = new char[file_len + 1];

	filesystem->Read((void*)GameInfo,file_len,fh);
	GameInfo[file_len] = 0; // null terminator

	filesystem->Close(fh);

	// Use GameInfo here...

	delete[] GameInfo;
}

This code opens gameinfo.txt in read-only mode, then stores its contents in GameInfo, a C string. Because the string was created with the new keyword, it is very important to call delete[] on it afterwards or the memory would be leaked. (If you know in advance the size of the file, you can do char MyString[the_length] as normal, and not risk any leaks.)

There is also a helper function that handles open/read/close for you in one swoop:

#include "Filesystem.h"
#include "utlbuffer.h"

CUtlBuffer buf;
if ( filesystem->ReadFile("gameinfo.txt","GAME",buf) )
{
	char* GameInfo = new char[buf.Size() + 1];
	buf.GetString(GameInfo);
	GameInfo[buf.Size()] = 0; // null terminator

	// Use GameInfo here...

	delete[] GameInfo;
}

To do: Is there a better way of reading from a CUtlBuffer?

Writing

FileHandle_t fh = filesystem->Open( "mylog.log", "a+", "MOD");

if (fh)
{
	filesystem->FPrintf(fh, "%s", "Logging A test line...");

	char* text = "Logging another test line...";
	filesystem->Write(text, V_strlen(text), fh);  

	filesystem->Close(fh);
}

There is also filesystem->WriteFile(char* name,char* path,CUtlBuffer &buf).

Searching

FileFindHandle_t findHandle; // note: FileFINDHandle

const char *pFilename = filesystem->FindFirstEx( "*.*", "MOD", &findHandle );
while (pFilename)
{
	Msg("%s\n",pFilename);
	pFilename = filesystem->FindNext( findHandle );
}

filesystem->FindClose( findHandle );

This code lists all files and folders in the gameinfo.txt folder. The Find functions do not search subfolders, and return only the filename matched, not its entire path.

To iterate over subfolders, use filesystem->FindIsDirectory() and alter the search terms (e.g. materials\\*.*).

Directories

Creating

char* path = "test_dir/subdir";
V_FixSlashes(path);
filesystem->CreateDirHierarchy(path,"DEFAULT_WRITE_PATH");

Removing

IFileSystem does not include support for deleting directories, and neither does the C++ standard. You'll need to either not delete them, or fall back on platform-specific APIs.

One function you will find useful when doing this is filesystem->RelativePathToFullPath.

Mounting Steam Application Content

The following code segment should be added to both CHLClient::Init in src\cl_dll\cdll_client_int.cpp and CServerGameDLL::DLLInit in src\dlls\GameInterface.cpp after filesystem is initialized.

	filesystem->AddSearchPath("path", "GAME");
	if(filesystem->MountSteamContent(-id)==FILESYSTEM_MOUNT_FAILED)
		return false;

path should be retrieved from Game Name Abbreviations

id should be retrieved from Steam Application IDs

- is a negative sign (Subtract) and is required to mount content properly.

Interface Details

The IFileSystem interface provides routines for accessing content on both the local drives, and in packages.

Search Paths

File paths are usually formatted as a relative path to the current folder. For example:

.\materials\Brick\brickfloor001a.vtf

Content from several sources is merged into this common root folder using search paths. This provides useful, however complex to the uninitiated, behaviour for accessing content. While the file used in the example above originates from the source 2007 shared material.gcf, not all files found in the .\materials folder will originate from the same place. Another example:

.\materials\myCustomMadeTexture.vtf

This file would most likely reside on the hard disk, in a mod folder, under its materials directory. The full path may be something like this:

C:\Program Files\Steam\SteamApps\SourceMods\MyMod\Materials\myCustomMadeTexture.vtf

The IFileSystem function GetSearchPaths retrieves a list of the search paths used to achieve this behaviour. Typical paths that would be returned:

C:\program files\steam\steamapps\sourcemods\mymod
C:\program files\steam\steamapps\username\sourcesdk\bin\orangebox\hl2

When the calling application attempts to open .\materials\myCustomMadeTexture.vtf, this first search path allows it to be interpreted as the full path. There may be many more search paths than these two. Whichever search path first finds an existing file, is the search path that is used. Hence, the order of the search paths is significant, and the GetSearchPaths function returns them in the correct order.

While this makes clear how hard disk files are found, the matter of mounted content requires further explanation.

Mounted Content

The second search path example above is what allows mounted content to be found. If one examines this folder on their hard disk, they will find little there. However, the steam file system can mount content (typically based on AppId) as paths in the local file system. When the IFileSystem is created, the content is mounted to the current directory of the calling code. Note, however that this directory should NOT be ...\orangebox\hl2. This is because the root of the associated packs are mounted to the current directory. GCFs such as the source 2007 shared material.gcf contain a folder called hl2 inside their root. When the GCF is mounted, this hl2 folder inside the gcf becomes ...orangebox\hl2. Therefore, when the IFileSystem is Loaded/Connected, the current directory should first be changed to:

C:\program files\steam\steamapps\username\sourcesdk\bin\orangebox

Or the appropriate path for the version of the source SDK being used.

Instantiating an IFileSystem

Though the IFileSystem interface cannot itself be instantiated, the closed source class that implements it can be, which provides us with a working IFileSystem. Several steps must be taken to retrieve an IFileSystem that functions as expected (functions at all).

Information that must be known:

  • Whether steam is running or not
  • The game directory (for example: c:\program files\steam\steamapps\sourcemods\mymod)
  • the SDK base directory (C:\program files\steam\steamapps\username\sourcesdk\bin\orangebox)
  • Whether or not to mount ExtraAppId
Is Steam Running?

The easiest way to do this is to check for steam.exe as a running process. However, this is not the safest way to allow the application to proceed. (For more details, see Api.cpp API::FileSystemReady in the DuctTape project). If Steam is not ready to provide the file system interface, the calling application will terminate immediately with a message printed to the debug output: "steam is not running"

Preparing the Runtime Environment
  • The location of steam.dll must be in the path environment variable.
  • If steam is an NT service (running in Vista) then the SteamAppUser environment variable must be set to the users name. This can be retrieved from the AutoLoginUser key in file:
steam install path\config\SteamAppData.vdf
  • The SteamAppId environment variable must be set to the AppId found in the mods/games gameinfo.txt
  • Set the sourcesdk environment variable to the SDK base directory
  • Change the current directory to the SDK base directory
Loading the FileSystem Module

The full path of filesystem_steam.dll must be determined, which resides in the source sdk's bin directory (orangebox\bin). This file must be used from this location, or mounted content will not be retrievable.

  • Call the Sys_LoadInterface function. It returns true on success:
CSysModule** module;
IFileSystem* fileSystem;
Sys_LoadInterface(fullPathToFileSystemDLL, "VFileSystem017", module, (void**)&fileSystem);
  • Connect the file system. It returns true on success:
fileSystem->Connect(Sys_GetFactoryThis());
  • Initialize the files system. It returns INIT_OK on success.
fileSystem->Init();
  • At this point, current directory can be restored.
Mount the extra content

If SDK extra content is to be mounted, the ToolsAppID must be retrieved from gameinfo.txt. Currently it is 211.

fileSystem->MountSteamContent(toolsAppId);
Load Search Paths

The search paths must be loaded from gameinfo.txt. For a thorough example of how this is to be done, see filesystem_init.cpp FileSystem_LoadSearchPaths in the DuctTape project. A simple explanation of how search paths are loaded can be found in the unmodified gameinfo.txt of a new mod.

See Also