r5sdk/r5dev/engine/cmodel_bsp.cpp

526 lines
17 KiB
C++
Raw Normal View History

//=============================================================================//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "core/stdafx.h"
#include "tier0/memstd.h"
#include "tier0/jobthread.h"
#include "tier1/fmtstr.h"
#include "tier1/keyvalues.h"
#include "tier2/fileutils.h"
#include "engine/sys_dll2.h"
#include "engine/host_cmd.h"
#include "engine/cmodel_bsp.h"
#include "rtech/pak/pakstate.h"
#include "rtech/pak/pakparse.h"
#include "rtech/pak/paktools.h"
#include "rtech/pak/pakstream.h"
#include "vpklib/packedstore.h"
#include "datacache/mdlcache.h"
#include "filesystem/filesystem.h"
#ifndef DEDICATED
#include "client/clientstate.h"
#endif // !DEDICATED
2023-09-05 17:34:22 +02:00
CUtlVector<CUtlString> g_InstalledMaps;
CFmtStrN<MAX_MAP_NAME> s_CurrentLevelName;
static CustomPakData_t s_customPakData;
static KeyValues* s_pLevelSetKV = nullptr;
//-----------------------------------------------------------------------------
// Purpose: load a custom pak and add it to the list
//-----------------------------------------------------------------------------
PakHandle_t CustomPakData_t::LoadAndAddPak(const char* const pakFile)
{
if (numHandles >= MAX_CUSTOM_PAKS)
{
Error(eDLL_T::ENGINE, NO_ERROR, "Tried to load pak '%s', but already reached the SDK's limit of %d!\n", pakFile, MAX_CUSTOM_PAKS);
return PAK_INVALID_HANDLE;
}
const PakHandle_t pakId = g_pakLoadApi->LoadAsync(pakFile, AlignedMemAlloc(), 4, 0);
// failure, don't add and return the invalid handle.
if (pakId == PAK_INVALID_HANDLE)
return pakId;
handles[numHandles++] = pakId;
return pakId;
}
//-----------------------------------------------------------------------------
// Purpose: unloads all active custom pak handles
//-----------------------------------------------------------------------------
void CustomPakData_t::UnloadAndRemoveAll()
{
for (; numHandles-1 >= CustomPakData_t::PAK_TYPE_COUNT; numHandles--)
{
const PakHandle_t pakId = handles[numHandles-1];
if (pakId == PAK_INVALID_HANDLE)
{
assert(0); // invalid handles should not be inserted
return;
}
g_pakLoadApi->UnloadAsync(pakId);
handles[numHandles-1] = PAK_INVALID_HANDLE;
}
}
//-----------------------------------------------------------------------------
// Purpose: loads the base SDK pak file by type
//-----------------------------------------------------------------------------
PakHandle_t CustomPakData_t::LoadBasePak(const char* const pakFile, const EPakType type)
{
const PakHandle_t pakId = g_pakLoadApi->LoadAsync(pakFile, AlignedMemAlloc(), 4, 0);
// the file is most likely missing
assert(pakId != PAK_INVALID_HANDLE);
handles[type] = pakId;
return pakId;
}
//-----------------------------------------------------------------------------
// Purpose: unload the SDK base pak file by type
//-----------------------------------------------------------------------------
void CustomPakData_t::UnloadBasePak(const EPakType type)
{
const PakHandle_t pakId = handles[type];
// only unload if it was actually successfully loaded
if (pakId != PAK_INVALID_HANDLE)
{
g_pakLoadApi->UnloadAsync(pakId);
handles[type] = PAK_INVALID_HANDLE;
}
}
//-----------------------------------------------------------------------------
// Purpose: checks if level has changed
// Input : *pszLevelName -
// Output : true if level name deviates from previous level
//-----------------------------------------------------------------------------
bool Mod_LevelHasChanged(const char* const pszLevelName)
{
return (V_strcmp(pszLevelName, s_CurrentLevelName.String()) != NULL);
}
//-----------------------------------------------------------------------------
// Purpose: gets all installed maps
//-----------------------------------------------------------------------------
void Mod_GetAllInstalledMaps()
{
CUtlVector<CUtlString> fileList;
AddFilesToList(fileList, "vpk", "vpk", nullptr, '/');
std::cmatch regexMatches;
std::lock_guard<std::mutex> l(g_InstalledMapsMutex);
2023-09-05 17:34:22 +02:00
g_InstalledMaps.Purge(); // Clear current list.
FOR_EACH_VEC(fileList, i)
{
const CUtlString& filePath = fileList[i];
2023-07-15 22:03:33 +02:00
const char* pFileName = strrchr(filePath.Get(), '/')+1;
// Should always point right in front of the last
// slash, as the files are loaded from 'vpk/'.
Assert(pFileName);
std::regex_search(pFileName, regexMatches, g_VpkDirFileRegex);
if (!regexMatches.empty())
{
const std::sub_match<const char*>& match = regexMatches[2];
2023-09-05 17:34:22 +02:00
if (match.compare("frontend") == 0)
continue; // Frontend contains no BSP's.
2023-09-05 17:34:22 +02:00
else if (match.compare("mp_common") == 0)
{
2023-09-05 17:34:22 +02:00
if (!g_InstalledMaps.HasElement("mp_lobby"))
g_InstalledMaps.AddToTail("mp_lobby");
continue; // Common contains mp_lobby.
}
else
{
const string mapName = match.str();
if (!g_InstalledMaps.HasElement(mapName.c_str()))
g_InstalledMaps.AddToTail(mapName.c_str());
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: processes queued pak files
//-----------------------------------------------------------------------------
void Mod_QueuedPakCacheFrame()
{
#ifndef DEDICATED
bool bUnconnected = !(*g_pClientState_Shifted)->IsConnected();
#else // !DEDICATED
bool bUnconnected = true; // Always true for dedicated.
#endif
bool startFromFirst = false;
if (Pak_StreamingDownloadFinished() && Pak_GetNumStreamableAssets() && bUnconnected)
{
*g_pPakPrecacheJobFinished = false;
startFromFirst = true;
}
else if (*g_pPakPrecacheJobFinished)
{
return;
}
if (!FileSystem()->ResetItemCache() || *g_pNumPrecacheItemsMTVTF)
{
return;
}
const char** pPakName = &g_commonPakData[0].basePakName;
int i;
for (i = 0; i < 5; ++i)
{
if (*((_BYTE*)pPakName - 268))
break;
const char* pakName = g_commonPakData[i].pakName;
const int64_t v4 = *pPakName - pakName;
int v5;
int v6;
do
{
v5 = (unsigned __int8)pakName[v4];
v6 = (unsigned __int8)*pakName - v5;
if (v6)
break;
++pakName;
} while (v5);
if (v6)
break;
pPakName += 35;
}
int startIndex = 0;
if (!startFromFirst)
startIndex = i; // start from last pre-cached
const int numToProcess = startIndex;
if (startIndex <= 4)
{
int numLeftToProcess = 4;
CommonPakData_t* data = &g_commonPakData[4];
do
{
if (*data->pakName)
{
PakLoadedInfo_s* const pakInfo = Pak_GetPakInfo(data->pakId);
PakStatus_e status;
// TODO: revisit this, this appears incorrect but also the way
// respawn does this. it this always supposed to be true on
// retail builds?
bool keepLoaded = true;
data->keepLoaded = true;
if (pakInfo->handle == data->pakId)
{
status = pakInfo->status;
keepLoaded = data->keepLoaded;
}
else
{
status = PAK_STATUS_INVALID_PAKHANDLE;
keepLoaded = true;
}
if (!keepLoaded || status == PAK_STATUS_LOADED)
{
// SDK pak files must be unloaded before the engine pak files,
// as we use assets within engine pak files.
switch (numLeftToProcess)
{
#ifndef DEDICATED
case CommonPakData_t::PAK_TYPE_UI_GM:
s_customPakData.UnloadBasePak(CustomPakData_t::PAK_TYPE_UI_SDK);
break;
#endif // !DEDICATED
case CommonPakData_t::PAK_TYPE_COMMON:
g_StudioMdlFallbackHandler.Clear();
break;
case CommonPakData_t::PAK_TYPE_COMMON_GM:
s_customPakData.UnloadBasePak(CustomPakData_t::PAK_TYPE_COMMON_SDK);
break;
case CommonPakData_t::PAK_TYPE_LOBBY:
s_customPakData.basePaksLoaded = false;
break;
default:
break;
}
// The old gather props is set if a model couldn't be
// loaded properly. If we unload level assets, we just
// enable the new implementation again and re-evaluate
// on the next level load. If we load a missing/bad
// model again, we toggle the old implementation as
// otherwise the fallback models won't render; the new
// gather props solution does not attempt to obtain
// studio hardware data on bad mdl handles. See
// 'GatherStaticPropsSecondPass_PreInit()' for details.
g_StudioMdlFallbackHandler.DisableLegacyGatherProps();
g_pakLoadApi->UnloadAsync(data->pakId);
Mod_UnloadPakFile(); // Unload mod pak files.
if (s_pLevelSetKV)
{
// Delete current level settings if we drop all paks..
s_pLevelSetKV->DeleteThis();
s_pLevelSetKV = nullptr;
}
}
if (status && (unsigned int)(status - 13) > 1)
return;
data->keepLoaded = false;
data->pakName[0] = '\0';
data->pakId = PAK_INVALID_HANDLE;
}
--numLeftToProcess;
--data;
} while (numLeftToProcess >= numToProcess);
}
*g_pPakPrecacheJobFinished = true;
CommonPakData_t* commonData = g_commonPakData;
int it = 0;
char* name;
char* nameIt;
while (true)
{
name = g_commonPakData[it].pakName;
nameIt = name;
char c;
int v20;
do
{
c = (unsigned __int8)nameIt[(unsigned __int64)(commonData->basePakName - (const char*)name)];
v20 = (unsigned __int8)*nameIt - c;
if (v20)
break;
++nameIt;
} while (c);
if (!v20)
goto CHECK_FOR_FAILURE;
V_strncpy(name, commonData->basePakName, MAX_PATH);
if (*commonData->pakName)
break;
commonData->pakId = PAK_INVALID_HANDLE;
LOOP_AGAIN_OR_FINISH:
++it;
++commonData;
if (it >= 5)
{
if (*g_pPakPrecacheJobFinished)
{
__int64 pMTVFTaskItem = *g_pMTVFTaskItem;
if (pMTVFTaskItem)
{
if (!*(_BYTE*)(pMTVFTaskItem + 4))
{
JobFifoLock_s* const pakFifoLock = &g_pakGlobals->fifoLock;
if (g_pakGlobals->hasPendingUnloadJobs || g_pakGlobals->loadedPakCount != g_pakGlobals->requestedPakCount)
{
if (!JT_AcquireFifoLockOrHelp(pakFifoLock)
&& !JT_HelpWithJobTypes(g_pPakFifoLockWrapper, pakFifoLock, -1i64, 0i64))
{
JT_HelpWithJobTypesOrSleep(g_pPakFifoLockWrapper, pakFifoLock, -1i64, 0i64, 0i64, 1);
}
Mod_UnloadPendingAndPrecacheRequestedPaks();
if (ThreadInMainThread())
{
if (*g_bPakFifoLockAcquired)
{
*g_bPakFifoLockAcquired = 0;
JT_ReleaseFifoLock(pakFifoLock);
}
}
JT_ReleaseFifoLock(pakFifoLock);
pMTVFTaskItem = *g_pMTVFTaskItem;
}
FileSystem()->ResetItemCacheSize(256);
FileSystem()->PrecacheTaskItem(pMTVFTaskItem);
}
}
}
return;
}
}
if (it == CommonPakData_t::PAK_TYPE_LOBBY)
s_customPakData.basePaksLoaded = true;
if (s_customPakData.basePaksLoaded && !s_customPakData.levelResourcesLoaded)
{
Mod_PreloadLevelPaks(s_CurrentLevelName.String());
s_customPakData.levelResourcesLoaded = true;
}
commonData->pakId = g_pakLoadApi->LoadAsync(name, AlignedMemAlloc(), 4, 0);
#ifndef DEDICATED
if (it == CommonPakData_t::PAK_TYPE_UI_GM)
s_customPakData.LoadBasePak("ui_sdk.rpak", CustomPakData_t::PAK_TYPE_UI_SDK);
#endif // !DEDICATED
if (it == CommonPakData_t::PAK_TYPE_COMMON_GM)
s_customPakData.LoadBasePak("common_sdk.rpak", CustomPakData_t::PAK_TYPE_COMMON_SDK);
CHECK_FOR_FAILURE:
if (commonData->pakId != PAK_INVALID_HANDLE)
{
const PakLoadedInfo_s* const pli = Pak_GetPakInfo(commonData->pakId);
if (pli->handle != commonData->pakId || ((pli->status - 9) & 0xFFFFFFFB) != 0)
{
*g_pPakPrecacheJobFinished = false;
return;
}
}
goto LOOP_AGAIN_OR_FINISH;
}
//-----------------------------------------------------------------------------
// Purpose: load assets for level with fifolock.
// Input : *szLevelName -
// Output : true on success, false on failure
//-----------------------------------------------------------------------------
void Mod_LoadPakForMap(const char* const pszLevelName)
{
if (Mod_LevelHasChanged(pszLevelName))
s_customPakData.levelResourcesLoaded = false;
s_CurrentLevelName = pszLevelName;
// Dedicated should not load loadscreens.
#ifndef DEDICATED
v_Mod_LoadPakForMap(pszLevelName);
#endif // !DEDICATED
}
//-----------------------------------------------------------------------------
// Purpose: loads the level settings file, returns current if level hasn't changed.
// Input : *pszLevelName -
// Output : KeyValues*
//-----------------------------------------------------------------------------
KeyValues* Mod_GetLevelSettings(const char* const pszLevelName)
{
if (s_pLevelSetKV)
{
// If we didn't change the level, return the current one
if (s_customPakData.levelResourcesLoaded)
return s_pLevelSetKV;
s_pLevelSetKV->DeleteThis();
}
char szPathBuffer[MAX_PATH];
snprintf(szPathBuffer, sizeof(szPathBuffer), "scripts/levels/settings/%s.kv", pszLevelName);
s_pLevelSetKV = FileSystem()->LoadKeyValues(IFileSystem::TYPE_LEVELSETTINGS, szPathBuffer, "GAME");
return s_pLevelSetKV;
}
//-----------------------------------------------------------------------------
// Purpose: loads required pakfile assets for specified BSP level
// Input : &svSetFile -
//-----------------------------------------------------------------------------
void Mod_PreloadLevelPaks(const char* const pszLevelName)
{
KeyValues* const pSettingsKV = Mod_GetLevelSettings(pszLevelName);
if (!pSettingsKV)
return;
KeyValues* const pPakListKV = pSettingsKV->FindKey("PakList");
if (!pPakListKV)
return;
char szPathBuffer[MAX_PATH];
for (KeyValues* pSubKey = pPakListKV->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey())
{
if (!pSubKey->GetBool())
continue;
snprintf(szPathBuffer, sizeof(szPathBuffer), "%s.rpak", pSubKey->GetName());
const PakHandle_t nPakId = s_customPakData.LoadAndAddPak(szPathBuffer);
if (nPakId == PAK_INVALID_HANDLE)
Error(eDLL_T::ENGINE, NO_ERROR, "%s: unable to load pak '%s' results '%d'\n", __FUNCTION__, szPathBuffer, nPakId);
}
}
//-----------------------------------------------------------------------------
// Purpose: unloads all pakfiles loaded by the SDK
//-----------------------------------------------------------------------------
void Mod_UnloadPakFile(void)
{
s_customPakData.UnloadAndRemoveAll();
g_StudioMdlFallbackHandler.ClearBadModelHandleCache();
g_StudioMdlFallbackHandler.ClearSuppresionList();
}
void VModel_BSP::Detour(const bool bAttach) const
{
DetourSetup(&v_Mod_LoadPakForMap, &Mod_LoadPakForMap, bAttach);
DetourSetup(&v_Mod_QueuedPakCacheFrame, &Mod_QueuedPakCacheFrame, bAttach);
}