@@ -640,6 +640,174 @@ const Source1AppidInfo_t *GetKnownAppidInfo( uint32 nAppid )
640640 return nullptr ;
641641}
642642
643+ #ifdef BDSBASE
644+ #ifndef ENGINE_DLL
645+ // ---------------------------------------------------------------------------------------------
646+ // Purpose: This function gets the Steam installation path. Calls for platform specific things.
647+ // ---------------------------------------------------------------------------------------------
648+ static const char *GetSteamInstallationPath ()
649+ {
650+ // Steam path to pass over
651+ static char szSteamPath[1024 ] = {};
652+ #ifdef WIN32
653+ // Open the registry to look for the Steam installation.
654+ HKEY hKey = nullptr ;
655+ LSTATUS status = RegOpenKeyEx (
656+ HKEY_CURRENT_USER,
657+ TEXT ( " SOFTWARE\\ Valve\\ Steam" ),
658+ 0 ,
659+ KEY_READ,
660+ &hKey );
661+
662+ // Check result
663+ if ( status != ERROR_SUCCESS )
664+ return nullptr ;
665+
666+ // Query the SteamPath key
667+ char szPathBuf[1024 ] = {};
668+ DWORD dwBufSize = sizeof ( szPathBuf );
669+ DWORD dwType = 0 ;
670+ LSTATUS result = RegQueryValueEx (
671+ hKey,
672+ " SteamPath" ,
673+ NULL ,
674+ &dwType,
675+ reinterpret_cast <BYTE*>( szPathBuf ),
676+ &dwBufSize );
677+
678+ // check for the result
679+ if ( result != ERROR_SUCCESS )
680+ return nullptr ;
681+
682+ // Close the registry key, we're done with it
683+ RegCloseKey ( hKey );
684+
685+ // Put this in the steam path var.
686+ Q_strncpy ( szSteamPath, szPathBuf, sizeof ( szSteamPath ) );
687+ #else
688+ // No registry on Linux, look for the symlink.
689+ const char *pszHomeDir = getenv ( " HOME" );
690+ const char *pszSteamPath = " /.steam/steam" ;
691+ Q_snprintf ( szSteamPath, sizeof ( szSteamPath ), " %s%s" , pszHomeDir, pszSteamPath );
692+ #endif // WIN32 ELSE !WIN32
693+ return szSteamPath;
694+ }
695+
696+ // -----------------------------------------------------------------------------
697+ // Purpose: Function for mod based projects to find installed game directories
698+ // Since non-engine mods don't have access to SteamApps, they can use this.
699+ // This goes through three steps:
700+ // 1. Finds the main Steam installation in GetSteamInstallationPath (queries registry on Windows, checks through symlink on others)
701+ // 2. Parse the libraryfolders.vdf file in Steam's config folder to find the appID
702+ // 3. Find the appmanifest_appID.acf file and look for the name. If so, return the folder name
703+ // Note: Slashes are fixed right before it's returned.
704+ // Input: game appID
705+ // Output: returns the install directory IF it's installed, otherwise returns nullptr
706+ // -----------------------------------------------------------------------------
707+ static const char *GetAppInstallDirNoSteam ( int nAppID )
708+ {
709+ // First, get the Steam installation.
710+ char szSteamPath[1024 ] = {};
711+ Q_strncpy ( szSteamPath, GetSteamInstallationPath (), sizeof ( szSteamPath ) );
712+ if ( !szSteamPath || szSteamPath[0 ] == ' \0 ' )
713+ return nullptr ;
714+
715+ // Second, go to the libraryfolders.vdf and look if the appid is in them.
716+ char szLibraryFoldersFile[1024 ] = {};
717+ Q_snprintf ( szLibraryFoldersFile, sizeof ( szLibraryFoldersFile ), " %s%s" , szSteamPath, " /config/libraryfolders.vdf" );
718+
719+ // NOTE: ReadKeyValuesFile already deletes the KV if it doesn't exist
720+ // read the libraryfolders.vdf file
721+ KeyValues *pRootKV = ReadKeyValuesFile ( szLibraryFoldersFile );
722+ if ( !pRootKV )
723+ return nullptr ;
724+
725+ // Get a string representation of the Steam AppID passed
726+ char szAppID[32 ] = {};
727+ Q_snprintf ( szAppID, sizeof ( szAppID ), " %d" , nAppID );
728+
729+ // Path KV
730+ KeyValues *pPathKV = nullptr ;
731+
732+ // Go thru each possible install path until we find the app.
733+ FOR_EACH_TRUE_SUBKEY ( pRootKV, pKVPath )
734+ {
735+ // Check for the apps subkey
736+ KeyValues *pApps = pKVPath->FindKey ( " apps" );
737+ if ( !pApps )
738+ continue ;
739+
740+ // Try to find the appid path
741+ if ( pApps->FindKey ( szAppID ) )
742+ {
743+ // Okay, we found the app id key. now use the path key from the root KV
744+ pPathKV = pKVPath->FindKey ( " path" ); // This is null checked below.
745+ break ;
746+ }
747+ }
748+
749+ // Error out if we can't find it
750+ if ( !pPathKV )
751+ return nullptr ;
752+
753+ // Look for the game that this mod asked for
754+ char szInstallationPath[1024 ] = {};
755+ // copy this string over since we're gonna delete the KV
756+ Q_strncpy ( szInstallationPath, pPathKV->GetString (), sizeof ( szInstallationPath ) );
757+ // If it's empty somehow, error out
758+ if ( !szInstallationPath || szInstallationPath[0 ] == ' \0 ' )
759+ {
760+ // Clear this keyvalue.
761+ pRootKV->deleteThis ();
762+ pRootKV = nullptr ;
763+ return nullptr ;
764+ }
765+
766+ // we no longer need this KV
767+ pRootKV->deleteThis ();
768+ pRootKV = nullptr ;
769+
770+ // get the appmanifest_APPID.acf file
771+ char szAppManifestPath[1024 ] = {};
772+ Q_snprintf ( szAppManifestPath, sizeof ( szAppManifestPath ), " %s%s%s.acf" , szInstallationPath, " /steamapps/appmanifest_" , szAppID );
773+ // Reuse the root pointer to open the appmanifest file
774+ pRootKV = ReadKeyValuesFile ( szAppManifestPath );
775+
776+ // Error out if this file can't be found
777+ if ( !pRootKV )
778+ return nullptr ;
779+
780+ // Find the install dir
781+ KeyValues *pInstallDirKV = pRootKV->FindKey ( " installdir" );
782+
783+ // Error out if we can't find this key
784+ if ( !pInstallDirKV )
785+ return nullptr ;
786+
787+ // Get the game name to get it from the common dir.
788+ // Again, deleting the KV after this
789+ char szGameName[256 ] = {};
790+ Q_strncpy ( szGameName, pInstallDirKV->GetString (), sizeof ( szGameName ) );
791+
792+ if ( !szGameName || szGameName[0 ] == ' \0 ' )
793+ return nullptr ;
794+
795+ // Not needed anymore
796+ pRootKV->deleteThis ();
797+ pRootKV = nullptr ;
798+
799+ // Now that we got everything, put it all together
800+ static char szAbsoluteGameDirPath[1024 ] = {};
801+ Q_snprintf ( szAbsoluteGameDirPath, sizeof ( szAbsoluteGameDirPath ), " %s%s%s" , szInstallationPath, " /common/" , szGameName );
802+
803+ // Fix the slashes before passing it
804+ V_FixDoubleSlashes ( szAbsoluteGameDirPath );
805+ V_FixSlashes ( szAbsoluteGameDirPath );
806+ return szAbsoluteGameDirPath;
807+ }
808+ #endif // ENGINE_DLL
809+ #endif
810+
643811FSReturnCode_t FileSystem_LoadSearchPaths ( CFSSearchPathsInit &initInfo )
644812{
645813 if ( !initInfo.m_pFileSystem || !initInfo.m_pDirectoryName )
@@ -687,15 +855,86 @@ FSReturnCode_t FileSystem_LoadSearchPaths( CFSSearchPathsInit &initInfo )
687855 const char *pszPathID = pCur->GetName ();
688856 const char *pLocation = pCur->GetString ();
689857 const char *pszBaseDir = baseDir;
858+ #ifndef BDSBASE
690859#ifdef ENGINE_DLL
691860 char szAppInstallDir[ 1024 ];
861+ #endif
692862#endif
693863
694864 if ( !FileSystem_AllowedSearchPath ( pLocation ) )
695865 continue ;
696866
697867 if ( Q_stristr ( pLocation, APPID_PREFIX_TOKEN ) == pLocation )
698868 {
869+ #ifdef BDSBASE
870+ Location += strlen ( APPID_PREFIX_TOKEN );
871+ const char *pNumberLoc = pLocation;
872+ int nAppId = V_atoi ( pNumberLoc );
873+ pLocation = Q_stristr ( pLocation, " |" );
874+ if ( !pLocation )
875+ {
876+ Error ( " Malformed gameinfo.txt" );
877+ }
878+ pLocation += strlen ( " |" );
879+
880+ if ( !nAppId )
881+ {
882+ Error ( " Can't mount content from invalid appid." );
883+ }
884+
885+ // Get the known appid info
886+ const Source1AppidInfo_t *pKnownAppid = GetKnownAppidInfo ( nAppId );
887+ const char *pszAppName = pKnownAppid ? pKnownAppid->pszName : " Unknown" ;
888+
889+ // Store the app install directory here.
890+ char szAppInstallDir[1024 ] = {};
891+ #ifdef ENGINE_DLL
892+ if ( !SteamApps () )
893+ {
894+ Error ( " No SteamApps connection." );
895+ }
896+
897+ if ( !SteamApps ()->BIsSubscribedApp ( nAppId ) )
898+ {
899+ char szStoreCommand[4096 ];
900+ V_sprintf_safe ( szStoreCommand, " steam://store/%d" , nAppId );
901+ Plat_OpenURL ( szStoreCommand );
902+
903+ Error ( " This mod requires that you own %s (%d). Please purchase it, and install it to play this mod." , pszAppName, nAppId );
904+ }
905+
906+ if ( !SteamApps ()->BIsAppInstalled ( nAppId ) )
907+ {
908+ char szInstallCommand[4096 ];
909+ V_sprintf_safe ( szInstallCommand, " steam://install/%d" , nAppId );
910+ Plat_OpenURL ( szInstallCommand );
911+
912+ Error ( " This mod requires %s (%d) to be installed. Please install it to play this mod." , pszAppName, nAppId );
913+ }
914+
915+ uint32 unLength = SteamApps ()->GetAppInstallDir ( nAppId, szAppInstallDir, sizeof ( szAppInstallDir ) );
916+ if ( !unLength )
917+ {
918+ Error ( " Couldn't get install dir for appid: %d" , nAppId );
919+ }
920+ #else
921+ // Get the install directory.
922+ const char *pszAppDir = GetAppInstallDirNoSteam ( nAppId );
923+ if ( !pszAppDir || pszAppDir[0 ] == ' \0 ' )
924+ {
925+ // Tell them to install this mod, but we don't have SteamApps here, so let them see the
926+ // "no licenses error" if they don't own this game.
927+ char szInstallCommand[4096 ] = {};
928+ V_sprintf_safe ( szInstallCommand, " steam://install/%d" , nAppId );
929+ Plat_OpenURL ( szInstallCommand );
930+ Error ( " This mod requires %s (%d) to be installed. Please install it to play this mod." , pszAppName, nAppId );
931+ }
932+
933+ // Copy the result over to szAppInstallDir.
934+ V_strncpy ( szAppInstallDir, pszAppDir, sizeof ( szAppInstallDir ) );
935+ #endif // ENGINE_DLL ELSE !ENGINE_DLL
936+ pszBaseDir = szAppInstallDir;
937+ #else
699938#ifdef ENGINE_DLL
700939 pLocation += strlen ( APPID_PREFIX_TOKEN );
701940 const char *pNumberLoc = pLocation;
@@ -747,6 +986,7 @@ FSReturnCode_t FileSystem_LoadSearchPaths( CFSSearchPathsInit &initInfo )
747986 pszBaseDir = szAppInstallDir;
748987#else
749988 Error ( " Appid based mounting is not supported on non-engine DLL projects." );
989+ #endif
750990#endif
751991 }
752992 else if ( Q_stristr ( pLocation, GAMEINFOPATH_TOKEN ) == pLocation )
0 commit comments