Mounting multiple games: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(not just 3 or more gcfs; tip on hard-coded mounting)
(simplified code, avoiding weird errors in console but changing the gameinfo.txt format required)
Line 1: Line 1:
{{toc-right}}
This code extends the Orange Box's [[AdditionalContentId]] system to support '''mounting of multiple [[GCF|Game Cache Files]] via the [[gameinfo.txt]]'''. It can be used with the Ep1 codebase too.
This code extends the Orange Box's [[AdditionalContentId]] system to support '''mounting of multiple [[GCF|Game Cache Files]] via the [[gameinfo.txt]]'''. It can be used with the Ep1 codebase too.


{{warning|Your mod will run regardless of whether or not the user owns the game that you mount content for. If the game isn't owned the content will not be mounted, and error signs will appear everywhere instead!}}
{{warning|Your mod will run regardless of whether or not the user owns the game that you mount content for. If the game isn't owned the content will not be mounted, and error signs will appear everywhere instead!}}


If the content you want to mount is required in order to play your mod properly, you should hard-code the ID instead of relying it to be in the [[gameinfo.txt]].
If the content you want to mount is required in order to play your mod properly, you may want to hard-code the ID instead of relying it to be in the [[gameinfo.txt]]. Remember when doing this that the int you pass to <code>filesystem->MountSteamContent()</code> must be negative!
{{tip|If you want to hardcode mounting, the important function is <code>filesystem->MountSteamContent( int -Id )</code>. Yes, you need to negate the number.}}


== Client (cdll_client_init.cpp) ==
{{note|1=The code below is version 2, and has ditched [http://developer.valvesoftware.com/w/index.php?title=Mounting_multiple_games&oldid=131419 the previous subkey approach]. It is not compatible with the <code>AdditionalContentId { 220,380 }</code> gameinfo.txt scheme.}}


Search for the <code>MountAdditionalContent()</code> function. If you don't have it, just add it right above CHLClient::Init.
== Client ==


This is the code:
'''Search cdll_client_init.cpp for the <code>MountAdditionalContent()</code> function''', and replace it with the code below.


<source lang="cpp">static void MountAdditionalContent()
If you don't already <code>MountAdditionalContent()</code> have, just add this function above <code>CHLClient::Init</code>, then call it right before <code>if ( CommandLine()->FindParm( "-textmode" ) )</code>.
 
<source lang="cpp">
static void MountAdditionalContent()
{
{
KeyValues *pMainFile, *pExtraContent, *pContentKey;
KeyValues *pMainFile, *pFileSystemInfo;
bool bKVHack = true;
int nExtraContentId;
 
pMainFile = new KeyValues( "gameinfo.txt" );
pMainFile = new KeyValues( "gameinfo.txt" );
if ( pMainFile->LoadFromFile( filesystem, VarArgs("%s/gameinfo.txt", engine->GetGameDirectory()), "MOD" ) )
if ( pMainFile->LoadFromFile( filesystem, VarArgs("%s/gameinfo.txt", engine->GetGameDirectory()), "MOD" ) )
{
{
pExtraContent = pMainFile->FindKey( "FileSystem" );
pFileSystemInfo = pMainFile->FindKey( "FileSystem" );
if ( pExtraContent )
if (pFileSystemInfo)
{
for ( KeyValues *pKey = pFileSystemInfo->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
pExtraContent = pExtraContent->FindKey( "AdditionalContentId" );
if ( pExtraContent )
{
{
pContentKey = pExtraContent->GetFirstSubKey();
if ( strcmp(pKey->GetName(),"AdditionalContentId") == 0 )
if ( pContentKey )
{
do {
//HACK: key and value cannot be accesed uniformly
if( bKVHack )
nExtraContentId = V_atoi( pContentKey->GetName() );
else
nExtraContentId = pContentKey->GetInt();
 
if ( nExtraContentId == 0 )
{
if( !bKVHack && !pContentKey->GetNextKey() )
{
//the KeyValues will have been complaining about a missing key
Msg( "(Client) Dear user, this code has detected, that you've received one KeyValues Error in the console. Do not worry about it.\n" );
break;
}
Warning( "(Client) Extra content appId \'%s\' is no integer\n", bKVHack ? pContentKey->GetName() : pContentKey->GetString() );
}
else if ( nExtraContentId == INT_MAX || nExtraContentId == INT_MIN )
Warning( "(Client) Extra content appId \'%s\' is out of range\n", bKVHack ? pContentKey->GetName() : pContentKey->GetString() );
else if ( filesystem->MountSteamContent( -abs( nExtraContentId ) ) != FILESYSTEM_MOUNT_OK )
Warning( "(Client) Unable to mount extra content with appId: %i\n", nExtraContentId );
else
DevMsg( "(Client) Successfully mounted extra content with appId: %i\n", nExtraContentId );
 
//HACK
if( bKVHack ) {
bKVHack = false;
continue;
}
else
bKVHack = true;
 
pContentKey = pContentKey->GetNextKey();
} while( pContentKey );
}
else
{
{
nExtraContentId = pExtraContent->GetInt();
int appid = abs(pKey->GetInt());
if ( !nExtraContentId )
if (appid)
Warning( "(Client) AdditionalContentId specified with invalid appId: %s\n", pExtraContent->GetString() );
if( filesystem->MountSteamContent(-appid) != FILESYSTEM_MOUNT_OK )
else if ( filesystem->MountSteamContent( -abs( nExtraContentId ) ) != FILESYSTEM_MOUNT_OK )
Warning("Unable to mount extra content with appId: %i\n", appid);
Warning( "(Client) Unable to mount extra content with appId: %i\n", nExtraContentId );
else
DevMsg( "(Client) Successfully mounted extra content with appId: %i\n", nExtraContentId );
}
}
}
}
}
}
}
pMainFile->deleteThis();
pMainFile->deleteThis();
}</source>
}
</source>


If you did not have this function before, then call this function in CHLClient::Init right before
== Server ==
<source lang="cpp">if ( CommandLine()->FindParm( "-textmode" ) )
g_bTextMode = true;</source>


== Server (gameinterface.cpp) ==
'''Search gameinterface.cpp for the <code>MountAdditionalContent()</code> function.''' Replace it with this code.
Again, search for the function "static void MountAdditionalContent()" and if you don't have it, just add it right above CServerGameDLL::DLLInit.


This is the code:
If you don't have it, just add it right above <code>CServerGameDLL::DLLInit</code> and call before the <code>gpGlobals = pGlobals;</code> line in that func.


<source lang="cpp">static void MountAdditionalContent()
<source lang="cpp">
static void MountAdditionalContent()
{
{
KeyValues *pMainFile, *pExtraContent, *pContentKey;
KeyValues *pMainFile, *pFileSystemInfo;
bool bFileFound, bKVHack = true;
int nExtraContentId;
char gamePath[256];


engine->GetGameDir( gamePath, 256 );
char gamePath[MAX_PATH];
engine->GetGameDir( gamePath, MAX_PATH );
Q_StripTrailingSlash( gamePath );
Q_StripTrailingSlash( gamePath );
pMainFile = new KeyValues( "gameinfo.txt" );
pMainFile = new KeyValues( "gameinfo.txt" );
//On linux because of case sensitiviy we need to check for both.
#ifdef _LINUX
#ifdef _LINUX
bFileFound = pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/GameInfo.txt", gamePath), "MOD" );
//On linux because of case sensitivity we need to check for both.
if ( !bFileFound )
pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/GameInfo.txt", gamePath), "MOD" );
if (!pMainFile)
#endif
#endif
bFileFound = pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/gameinfo.txt", gamePath), "MOD" );
pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/gameinfo.txt", gamePath), "MOD" );
if ( bFileFound )
if (pMainFile)
{
{
pExtraContent = pMainFile->FindKey( "FileSystem" );
pFileSystemInfo = pMainFile->FindKey( "FileSystem" );
if ( pExtraContent )
if (pFileSystemInfo)
{
for ( KeyValues *pKey = pFileSystemInfo->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
pExtraContent = pExtraContent->FindKey( "AdditionalContentId" );
if ( pExtraContent )
{
{
pContentKey = pExtraContent->GetFirstSubKey();
if ( strcmp(pKey->GetName(),"AdditionalContentId") == 0 )
if ( pContentKey )
{
do {
//HACK: key and value cannot be accesed uniformly
if( bKVHack )
nExtraContentId = V_atoi( pContentKey->GetName() );
else
nExtraContentId = pContentKey->GetInt();
 
if ( nExtraContentId == 0 )
{
if( !bKVHack && !pContentKey->GetNextKey() )
{
//the KeyValues will have been complaining about a missing key
Msg( "(Server) Dear user, this code has detected, that you've received one KeyValues Error in the console. Do not worry about it.\n" );
break;
}
Warning( "(Server) Extra content appId \'%s\' is no integer\n", bKVHack ? pContentKey->GetName() : pContentKey->GetString() );
}
else if ( nExtraContentId == INT_MAX || nExtraContentId == INT_MIN )
Warning( "(Server) Extra content appId \'%s\' is out of range\n", bKVHack ? pContentKey->GetName() : pContentKey->GetString() );
else if ( filesystem->MountSteamContent( -abs( nExtraContentId ) ) != FILESYSTEM_MOUNT_OK )
Warning( "(Server) Unable to mount extra content with appId: %i\n", nExtraContentId );
else
DevMsg( "(Server) Successfully mounted extra content with appId: %i\n", nExtraContentId );
 
//HACK
if( bKVHack ) {
bKVHack = false;
continue;
}
else
bKVHack = true;
 
pContentKey = pContentKey->GetNextKey();
} while( pContentKey );
}
else
{
{
nExtraContentId = pExtraContent->GetInt();
int appid = abs(pKey->GetInt());
if ( nExtraContentId == 0 )
if (appid)
Warning( "(Server) AdditionalContentId specified with invalid appId: %s\n", pExtraContent->GetString() );
if( filesystem->MountSteamContent(-appid) != FILESYSTEM_MOUNT_OK )
else if ( filesystem->MountSteamContent( -abs( nExtraContentId ) ) != FILESYSTEM_MOUNT_OK )
Warning("Unable to mount extra content with appId: %i\n", appid);
Warning( "(Server) Unable to mount extra content with appId: %i\n", nExtraContentId );
else
DevMsg( "(Server) Successfully mounted extra content with appId: %i\n", nExtraContentId );
}
}
}
}
}
}
}
pMainFile->deleteThis();
pMainFile->deleteThis();
}</source>
}
 
</source>
If you did not have this function before, then call this function in CServerGameDLL::DLLInit right before <code>gpGlobals = pGlobals;</code>.


== Implementing in gameinfo.txt ==
== Implementing in gameinfo.txt ==


Now you can mount content by adding this after the ToolsAppId:
Now you can mount content by adding something like this after the ToolsAppId:


  AdditionalContentId
AdditionalContentId 220 //HL2
  {
AdditionalContentId 240 //CS Source
    220 //HL2
AdditionalContentId 280 //HL Source
    240 //CS Source
AdditionalContentId 320 //HL2 Deatmatch
    280 //HL Source
AdditionalContentId 340 //HL2 Lost Coast
    320 //HL2 Deatmatch
AdditionalContentId 360 //HL Deathmatch: Source
    340 //HL2 Lost Coast
AdditionalContentId 380 //Ep1
    360 //HL Deathmatch: Source
AdditionalContentId 400 //Portal
    380 //Ep1
AdditionalContentId 420 //Ep2
    400 //Portal
AdditionalContentId 440 //TF2
    420 //Ep2
AdditionalContentId 500 // L4D - has content incompatibilities!
    440 //TF2
AdditionalContentId 550 // L4D2 - as above!
    500 // L4D - has content incompatibilities!
    550 // L4D2 - as above!
  }


Don't forget to add the game's SearchPath too. Mounting a single id (<code>AdditionalContentId 220</code>) is still supported.
Don't forget to add the relevant [[Gameinfo.txt#SearchPaths|SearchPaths]] too.


== Bugs ==
== Bugs ==


Really big numbers will crash the game with an Error-Dialog by Valve. You could check if the value is within a certain range to stop this from happening, but I think the user will get by himself that this value cannot be mounted.
Really big numbers will crash the game with an Error-Dialog by Valve. You could check if the value is within a certain range to stop this from happening, but I think the user will get by himself that this value cannot be mounted.
A KeyValues-error will appear in the console when you mount an uneven number of games. Nope, neither me ([[User:Z33ky|z33ky]]) or Valve think it's unlucky to do that (or maybe I do..!), it's rather that the KeyValues-class will expect a value, when there is none left. You can fix this by modifying KeyValues::RecursiveLoadFromBuffer in tier1/KeyValues.cpp, compiling a new tier1.lib and link against that. You can either silently ignore that error (which means that it will be ignored for other KeyValues-buffers as well!) or think of how to ignore that error properly.
An alternative is to load the file contents into a buffer yourself, checking that out and cheating another value in, so the KeyValues-parser is happy when you then do LoadFromBuffer.
I ([[User:Z33ky|z33ky]]) decided not to do that so I don't have to merge that code and compile a new tier1.lib or copy it over when I port my code to an updated SDK version. And I found the alternative too messy for this little draw-back; it's probably the better way to go if you really don't want that error to show up though. Currently, the code simply tells the user not to worry about it.


{{confirm|filesystem->MountSteamContent returns FILESYSTEM_MOUNT_OK, even if the content is not available. There's nothing you can do about it, apart from poking some guy from Valvesoftware.}}
{{confirm|filesystem->MountSteamContent returns FILESYSTEM_MOUNT_OK, even if the content is not available. There's nothing you can do about it, apart from poking some guy from Valvesoftware.}}
Line 210: Line 110:


* [[IFileSystem]]
* [[IFileSystem]]
* [[IFileSystem#Mount_Steam_Application_Content|Mount Steam Application Content]]
* [[Gameinfo.txt]]
* [[Steam_Application_IDs]]
* [[Steam Application IDs]]


[[Category:Programming]]
[[Category:Programming]]
[[Category:Tutorials]]
[[Category:Tutorials]]

Revision as of 15:05, 6 November 2010

This code extends the Orange Box's AdditionalContentId system to support mounting of multiple Game Cache Files via the gameinfo.txt. It can be used with the Ep1 codebase too.

Warning.pngWarning:Your mod will run regardless of whether or not the user owns the game that you mount content for. If the game isn't owned the content will not be mounted, and error signs will appear everywhere instead!

If the content you want to mount is required in order to play your mod properly, you may want to hard-code the ID instead of relying it to be in the gameinfo.txt. Remember when doing this that the int you pass to filesystem->MountSteamContent() must be negative!

Note.pngNote:The code below is version 2, and has ditched the previous subkey approach. It is not compatible with the AdditionalContentId { 220,380 } gameinfo.txt scheme.

Client

Search cdll_client_init.cpp for the MountAdditionalContent() function, and replace it with the code below.

If you don't already MountAdditionalContent() have, just add this function above CHLClient::Init, then call it right before if ( CommandLine()->FindParm( "-textmode" ) ).

static void MountAdditionalContent()
{
	KeyValues *pMainFile, *pFileSystemInfo;
	
	pMainFile = new KeyValues( "gameinfo.txt" );
	if ( pMainFile->LoadFromFile( filesystem, VarArgs("%s/gameinfo.txt", engine->GetGameDirectory()), "MOD" ) )
	{
		pFileSystemInfo = pMainFile->FindKey( "FileSystem" );
		if (pFileSystemInfo)
			for ( KeyValues *pKey = pFileSystemInfo->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
			{
				if ( strcmp(pKey->GetName(),"AdditionalContentId") == 0 )
				{
					int appid = abs(pKey->GetInt());
					if (appid)
						if( filesystem->MountSteamContent(-appid) != FILESYSTEM_MOUNT_OK )
							Warning("Unable to mount extra content with appId: %i\n", appid);
				}
			}
	}
	pMainFile->deleteThis();
}

Server

Search gameinterface.cpp for the MountAdditionalContent() function. Replace it with this code.

If you don't have it, just add it right above CServerGameDLL::DLLInit and call before the gpGlobals = pGlobals; line in that func.

static void MountAdditionalContent()
{
	KeyValues *pMainFile, *pFileSystemInfo;

	char gamePath[MAX_PATH];
	engine->GetGameDir( gamePath, MAX_PATH );
	Q_StripTrailingSlash( gamePath );
		
	pMainFile = new KeyValues( "gameinfo.txt" );
#ifdef _LINUX
	//On linux because of case sensitivity we need to check for both.
	pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/GameInfo.txt", gamePath), "MOD" );
	if (!pMainFile)
#endif
	pMainFile->LoadFromFile( filesystem, UTIL_VarArgs("%s/gameinfo.txt", gamePath), "MOD" );
	
	if (pMainFile)
	{
		pFileSystemInfo = pMainFile->FindKey( "FileSystem" );
		if (pFileSystemInfo)
			for ( KeyValues *pKey = pFileSystemInfo->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
			{
				if ( strcmp(pKey->GetName(),"AdditionalContentId") == 0 )
				{
					int appid = abs(pKey->GetInt());
					if (appid)
						if( filesystem->MountSteamContent(-appid) != FILESYSTEM_MOUNT_OK )
							Warning("Unable to mount extra content with appId: %i\n", appid);
				}
			}
	}	
	pMainFile->deleteThis();
}

Implementing in gameinfo.txt

Now you can mount content by adding something like this after the ToolsAppId:

AdditionalContentId 220 //HL2
AdditionalContentId 240 //CS Source
AdditionalContentId 280 //HL Source
AdditionalContentId 320 //HL2 Deatmatch
AdditionalContentId 340 //HL2 Lost Coast
AdditionalContentId 360 //HL Deathmatch: Source
AdditionalContentId 380 //Ep1
AdditionalContentId 400 //Portal
AdditionalContentId 420 //Ep2
AdditionalContentId 440 //TF2
AdditionalContentId 500 // L4D - has content incompatibilities!
AdditionalContentId 550 // L4D2 - as above!

Don't forget to add the relevant SearchPaths too.

Bugs

Really big numbers will crash the game with an Error-Dialog by Valve. You could check if the value is within a certain range to stop this from happening, but I think the user will get by himself that this value cannot be mounted.

Confirm:filesystem->MountSteamContent returns FILESYSTEM_MOUNT_OK, even if the content is not available. There's nothing you can do about it, apart from poking some guy from Valvesoftware.

See also