diff --git a/README.md b/README.md
index b4070a9..ff45f34 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,13 @@ Bugfixed version of rehlds contains an additional cvars:
sv_rehlds_attachedentities_playeranimationspeed_fix // Fixes bug with gait animation speed increase when player has some attached entities (aiments). Can cause animation lags when cl_updaterate is low. Default: 0
+## Commands
+Bugfixed version of rehlds contains an additional commands:
+
+- rescount // Prints the total precached of resources count in server console
+
- reslist <sound | model | decal | generic | event> // Separately prints the details of the precached resources for sounds, models, decals, generic and events in server console. Useful for managing resources and dealing with the goldsource precache limits.
+
+
## Build instructions
There are several software requirements for building rehlds:
diff --git a/rehlds/HLTV/common/net_internal.h b/rehlds/HLTV/common/net_internal.h
index bb304d7..e2af351 100644
--- a/rehlds/HLTV/common/net_internal.h
+++ b/rehlds/HLTV/common/net_internal.h
@@ -44,7 +44,7 @@
// Response details about each player on the server
#define S2A_PLAYERS 'D'
-// Response as multi-packeted the rules the server is using
+// Number of rules + string key and string value pairs
#define S2A_RULES 'E'
/* ------ S2C_* - Server to client ------ */
diff --git a/rehlds/engine/host_cmd.cpp b/rehlds/engine/host_cmd.cpp
index c3580a1..e1edcbb 100644
--- a/rehlds/engine/host_cmd.cpp
+++ b/rehlds/engine/host_cmd.cpp
@@ -2662,7 +2662,7 @@ void Host_Kill_f(void)
Cmd_ForwardToServer();
return;
}
-
+
if (sv_player->v.health <= 0.0f
#ifdef REHLDS_FIXES
|| sv_player->v.deadflag != DEAD_NO
@@ -2985,6 +2985,76 @@ NOXREF void Host_Crash_f(void)
*p = 0xffffffff;
}
+#ifdef REHLDS_FIXES
+void Host_ResourcesCount_f()
+{
+ if (g_psv.num_resources <= 0) {
+ Con_Printf("--------------\nNo precached resources.\n\n");
+ return;
+ }
+
+ Con_Printf("\n %-4s : %-5s %-5s\n\n", "Type", "Total", "Limit");
+ Con_Printf(" model : %-5d %-5d\n", SV_CountResourceByType(t_model), MAX_MODELS - 2); // CL_LoadModel expects last model slot is empty
+ Con_Printf(" sound : %-5d %-5d\n", SV_CountResourceByType(t_sound), MAX_SOUNDS - 1);
+ Con_Printf(" generic : %-5d %-5d\n", SV_CountResourceByType(t_generic), ARRAYSIZE(g_rehlds_sv.precachedGenericResourceNames));
+ Con_Printf(" event : %-5d %-5d\n", SV_CountResourceByType(t_eventscript), MAX_EVENTS - 1);
+ Con_Printf(" decal : %-5d %-5d\n", SV_CountResourceByType(t_decal), MAX_DECALS - 1);
+ Con_Printf("------------------------\n%d Total of precached resource count\n\n", g_psv.num_resources, RESOURCE_MAX_COUNT);
+}
+
+void Host_ResourcesList_f()
+{
+ const char *pszType = Cmd_Argv(1);
+ if (Cmd_Argc() == 1
+ || (pszType[0]
+ && Q_stricmp(pszType, "sound")
+ && Q_stricmp(pszType, "model")
+ && Q_stricmp(pszType, "decal")
+ && Q_stricmp(pszType, "generic")
+ && Q_stricmp(pszType, "event")))
+ {
+ Con_Printf("Usage: reslist \n");
+ return;
+ }
+
+ resourcetype_t type;
+ switch (pszType[0])
+ {
+ default:
+ case 's': type = t_sound; break;
+ case 'm': type = t_model; break;
+ case 'd': type = t_decal; break;
+ case 'g': type = t_generic; break;
+ case 'e': type = t_eventscript; break;
+ }
+
+ size_t nWidthFileName = 8;
+ resource_t *pResourseList[RESOURCE_MAX_COUNT];
+ size_t nCountRes = SV_CountResourceByType(type, pResourseList, ARRAYSIZE(pResourseList), &nWidthFileName);
+
+ char szMD5Hash[9], szFlags[32];
+ Con_Printf("\n%4s %-4s : %-*s %-10s %-8s %-26s\n\n", "#", "Index", nWidthFileName, "FileName", "Size", "MD5", "Flags");
+ for (size_t i = 0; i < nCountRes; i++)
+ {
+ szFlags[0] = '\0';
+ if (pResourseList[i]->ucFlags & RES_CHECKFILE) {
+ Q_strlcat(szFlags, "CHECKFILE");
+ }
+ if (pResourseList[i]->ucFlags & RES_FATALIFMISSING) {
+ Q_strlcat(szFlags, " FATALIFMISSING");
+ }
+
+ TrimSpace(szFlags, szFlags);
+
+ // copy only 4 bytes
+ Q_strlcpy(szMD5Hash, MD5_Print(pResourseList[i]->rgucMD5_hash));
+ Con_Printf("%4d. %-4d : %-*s %-10s %-8s %-26s\n", i + 1, pResourseList[i]->nIndex, nWidthFileName, pResourseList[i]->szFileName, va("%.2fK", pResourseList[i]->nDownloadSize / 1024.0f), szMD5Hash, (szFlags[0] == '\0') ? "-" : szFlags);
+ }
+
+ Con_Printf("--------------\n%d Total %s's\n\n", nCountRes, pszType);
+}
+#endif
+
void Host_InitCommands(void)
{
#ifdef HOOK_ENGINE
@@ -3100,6 +3170,11 @@ void Host_InitCommands(void)
Cmd_AddCommand("heartbeat", Master_Heartbeat_f);
#endif // HOOK_ENGINE
+#ifdef REHLDS_FIXES
+ Cmd_AddCommand("rescount", Host_ResourcesCount_f);
+ Cmd_AddCommand("reslist", Host_ResourcesList_f);
+#endif
+
Cvar_RegisterVariable(&gHostMap);
Cvar_RegisterVariable(&voice_recordtofile);
Cvar_RegisterVariable(&voice_inputfromfile);
diff --git a/rehlds/engine/host_cmd.h b/rehlds/engine/host_cmd.h
index ec9b3b2..49cdf30 100644
--- a/rehlds/engine/host_cmd.h
+++ b/rehlds/engine/host_cmd.h
@@ -205,6 +205,12 @@ void Host_KillServer_f(void);
void Host_VoiceRecordStart_f(void);
void Host_VoiceRecordStop_f(void);
void Host_Crash_f(void);
+
+#ifdef REHLDS_FIXES
+void Host_ResourcesList_f();
+void Host_ResourcesCount_f();
+#endif
+
void Host_InitCommands(void);
void SV_CheckBlendingInterface(void);
void SV_CheckSaveGameCommentInterface(void);
diff --git a/rehlds/engine/net.h b/rehlds/engine/net.h
index 9f3fa98..151f0cd 100644
--- a/rehlds/engine/net.h
+++ b/rehlds/engine/net.h
@@ -57,7 +57,7 @@ const char M2S_REQUESTRESTART = 'O';
// Response details about each player on the server
const char S2A_PLAYERS = 'D';
-// Response as multi-packeted the rules the server is using
+// Number of rules + string key and string value pairs
const char S2A_RULES = 'E';
// info request
diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h
index a92163e..1b81492 100644
--- a/rehlds/engine/server.h
+++ b/rehlds/engine/server.h
@@ -748,6 +748,7 @@ void SV_SendClientMessages(void);
void SV_ExtractFromUserinfo(client_t *cl);
int SV_ModelIndex(const char *name);
void SV_AddResource(resourcetype_t type, const char *name, int size, unsigned char flags, int index);
+size_t SV_CountResourceByType(resourcetype_t type, resource_t **pResourceList = nullptr, size_t nListMax = 0, size_t *nWidthFileNameMax = nullptr);
void SV_CreateGenericResources(void);
void SV_CreateResourceList(void);
void SV_ClearCaches(void);
diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp
index a5cc56f..5882001 100644
--- a/rehlds/engine/sv_main.cpp
+++ b/rehlds/engine/sv_main.cpp
@@ -5105,26 +5105,63 @@ void EXT_FUNC SV_AddResource(resourcetype_t type, const char *name, int size, un
resource_t *r;
#ifdef REHLDS_FIXES
if (g_psv.num_resources >= ARRAYSIZE(g_rehlds_sv.resources))
-#else // REHLDS_FIXES
+#else
if (g_psv.num_resources >= MAX_RESOURCE_LIST)
-#endif // REHLDS_FIXES
+#endif
{
Sys_Error("%s: Too many resources on server.", __func__);
}
#ifdef REHLDS_FIXES
r = &g_rehlds_sv.resources[g_psv.num_resources++];
-
Q_memset(r, 0, sizeof(*r));
-#else // REHLDS_FIXES
+#else
r = &g_psv.resourcelist[g_psv.num_resources++];
#endif
+
r->type = type;
- Q_strncpy(r->szFileName, name, sizeof(r->szFileName) - 1);
- r->szFileName[sizeof(r->szFileName) - 1] = 0;
r->ucFlags = flags;
r->nDownloadSize = size;
r->nIndex = index;
+
+ Q_strlcpy(r->szFileName, name);
+}
+
+size_t SV_CountResourceByType(resourcetype_t type, resource_t **pResourceList, size_t nListMax, size_t *nWidthFileNameMax)
+{
+ if (type < t_sound || type >= rt_max)
+ return 0;
+
+ if (pResourceList && nListMax <= 0)
+ return 0;
+
+ resource_t *r;
+#ifdef REHLDS_FIXES
+ r = &g_rehlds_sv.resources[0];
+#else
+ r = &g_psv.resourcelist[0];
+#endif
+
+ size_t nCount = 0;
+ for (int i = 0; i < g_psv.num_resources; i++, r++)
+ {
+ if (r->type != type)
+ continue;
+
+ if (r->type == t_decal && r->nIndex >= MAX_DECALS)
+ continue;
+
+ if (pResourceList)
+ pResourceList[nCount] = r;
+
+ if (nWidthFileNameMax)
+ *nWidthFileNameMax = Q_max(*nWidthFileNameMax, Q_strlen(r->szFileName));
+
+ if (++nCount >= nListMax && nListMax > 0)
+ break;
+ }
+
+ return nCount;
}
#ifdef REHLDS_FIXES