From a680f18ee1e7eb8c39fbdc45682163ca9477d783 Mon Sep 17 00:00:00 2001 From: Sergey Shorokhov Date: Mon, 8 May 2023 12:24:16 +0300 Subject: [PATCH 01/30] SV_BuildSoundMsg: fix '\n' in args check - fix #965 --- rehlds/engine/sv_main.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 83a3e3cf7..9a34fa783 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -680,22 +680,22 @@ qboolean SV_BuildSoundMsg(edict_t *entity, int channel, const char *sample, int if (volume < 0 || volume > 255) { - Con_Printf("%s: volume = %i", __func__, volume); + Con_Printf("%s: volume = %i\n", __func__, volume); volume = (volume < 0) ? 0 : 255; } if (attenuation < 0.0f || attenuation > 4.0f) { - Con_Printf("%s: attenuation = %f", __func__, attenuation); + Con_Printf("%s: attenuation = %f\n", __func__, attenuation); attenuation = (attenuation < 0.0f) ? 0.0f : 4.0f; } if (channel < 0 || channel > 7) { - Con_Printf("%s: channel = %i", __func__, channel); + Con_Printf("%s: channel = %i\n", __func__, channel); channel = (channel < 0) ? CHAN_AUTO : CHAN_NETWORKVOICE_BASE; } if (pitch < 0 || pitch > 255) { - Con_Printf("%s: pitch = %i", __func__, pitch); + Con_Printf("%s: pitch = %i\n", __func__, pitch); pitch = (pitch < 0) ? 0 : 255; } @@ -707,7 +707,7 @@ qboolean SV_BuildSoundMsg(edict_t *entity, int channel, const char *sample, int sound_num = Q_atoi(sample + 1); if (sound_num >= CVOXFILESENTENCEMAX) { - Con_Printf("%s: invalid sentence number: %s", __func__, sample + 1); + Con_Printf("%s: invalid sentence number: %s\n", __func__, sample + 1); return FALSE; } } From 0c5ce53666811c4243ed5aa0c755dab26d2283d4 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 2 Jul 2023 03:45:49 +0700 Subject: [PATCH 02/30] Allow the clients to connect on the server of different game Fixed #975 Client should be use "setinfo _gd " --- rehlds/engine/sv_main.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 9a34fa783..fa79fe9c5 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -1112,8 +1112,18 @@ void SV_SendServerinfo_internal(sizebuf_t *msg, client_t *client) else MSG_WriteByte(msg, 0); - COM_FileBase(com_gamedir, message); - MSG_WriteString(msg, message); + const char *pszGameDir = message; + +#ifdef REHLDS_FIXES + // Give the client a chance to connect in to the server with different game + const char *gd = Info_ValueForKey(client->userinfo, "_gd"); + if (gd[0]) + pszGameDir = gd; + else +#endif + COM_FileBase(com_gamedir, message); + + MSG_WriteString(msg, pszGameDir); MSG_WriteString(msg, Cvar_VariableString("hostname")); MSG_WriteString(msg, g_psv.modelname); From 8841ba4aecb8f428fbca9e91844b9ab5ef39e95b Mon Sep 17 00:00:00 2001 From: s1lentq Date: Tue, 4 Jul 2023 01:33:41 +0700 Subject: [PATCH 03/30] GetAttachment: Added attachment index bounds check --- rehlds/engine/r_studio.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rehlds/engine/r_studio.cpp b/rehlds/engine/r_studio.cpp index 5b12b835e..906c205a4 100644 --- a/rehlds/engine/r_studio.cpp +++ b/rehlds/engine/r_studio.cpp @@ -906,14 +906,23 @@ void EXT_FUNC GetAttachment(const edict_t *pEdict, int iAttachment, float *rgflO mstudioattachment_t *pattachment; vec3_t angles; - angles[0] = -pEdict->v.angles[0]; - angles[1] = pEdict->v.angles[1]; - angles[2] = pEdict->v.angles[2]; - pstudiohdr = (studiohdr_t *)Mod_Extradata(g_psv.models[pEdict->v.modelindex]); + +#ifdef REHLDS_FIXES + if (!pstudiohdr) + return; + + if (iAttachment < 0 || iAttachment >= pstudiohdr->numattachments) + return; // invalid attachment +#endif + pattachment = (mstudioattachment_t *)((char *)pstudiohdr + pstudiohdr->attachmentindex); pattachment += iAttachment; + angles[0] = -pEdict->v.angles[0]; + angles[1] = pEdict->v.angles[1]; + angles[2] = pEdict->v.angles[2]; + g_pSvBlendingAPI->SV_StudioSetupBones( g_psv.models[pEdict->v.modelindex], pEdict->v.frame, From 918612fa238355d01bfdef15e970de2f67695b81 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Tue, 4 Jul 2023 01:39:26 +0700 Subject: [PATCH 04/30] GetBonePosition: Added bone index bounds check --- rehlds/engine/r_studio.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rehlds/engine/r_studio.cpp b/rehlds/engine/r_studio.cpp index 906c205a4..4124bf67f 100644 --- a/rehlds/engine/r_studio.cpp +++ b/rehlds/engine/r_studio.cpp @@ -881,6 +881,15 @@ void EXT_FUNC AnimationAutomove(const edict_t *pEdict, float flTime) void EXT_FUNC GetBonePosition(const edict_t *pEdict, int iBone, float *rgflOrigin, float *rgflAngles) { pstudiohdr = (studiohdr_t *)Mod_Extradata(g_psv.models[pEdict->v.modelindex]); + +#ifdef REHLDS_FIXES + if (!pstudiohdr) + return; + + if (iBone < 0 || iBone >= pstudiohdr->numbones) + return; // invalid bone +#endif + g_pSvBlendingAPI->SV_StudioSetupBones( g_psv.models[pEdict->v.modelindex], pEdict->v.frame, From f955b07b69a10db72290b2b35e3579151e9dc810 Mon Sep 17 00:00:00 2001 From: Sergey Shorokhov Date: Mon, 3 Jul 2023 22:02:28 +0300 Subject: [PATCH 05/30] Revert "change destinition folder for linux build (#842)" (#977) This reverts commit 9508c8376bd14fbcd92beaa93b191ee4fb1c90a3. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ddedaf4a3..29a64b6d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -277,7 +277,7 @@ jobs: github.event.action == 'published' && startsWith(github.ref, 'refs/tags/') run: | - 7z a -tzip rehlds-bin-${{ env.APP_VERSION }}.zip bin/linux32/ hlsdk/ + 7z a -tzip rehlds-bin-${{ env.APP_VERSION }}.zip bin/ hlsdk/ 7z a -t7z -m0=lzma2 -mx=9 -mfb=64 -aoa rehlds-dbg-${{ env.APP_VERSION }}.7z debug/ - name: Publish artifacts From 2ba27d409c364d12c01b6b72813ac4b991a6e224 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 3 Sep 2023 15:08:30 +0700 Subject: [PATCH 06/30] Fixed reversing mistake, missing checking string for null --- rehlds/engine/pr_cmds.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rehlds/engine/pr_cmds.cpp b/rehlds/engine/pr_cmds.cpp index e1d956a7e..e83cbb429 100644 --- a/rehlds/engine/pr_cmds.cpp +++ b/rehlds/engine/pr_cmds.cpp @@ -1447,6 +1447,9 @@ int EXT_FUNC PF_precache_model_I_internal(const char *s) { for (int i = 0; i < MAX_MODELS; i++) { + if (!g_psv.model_precache[i]) + continue; + // use case-sensitive names to increase performance #ifdef REHLDS_FIXES if (!Q_strcmp(g_psv.model_precache[i], s)) @@ -1545,7 +1548,7 @@ int EXT_FUNC PF_precache_generic_I_internal(const char *s) { for (int i = 0; i < MAX_GENERIC; i++) { - if (!Q_stricmp(g_psv.generic_precache[i], s)) + if (g_psv.generic_precache[i] && !Q_stricmp(g_psv.generic_precache[i], s)) return i; } Host_Error("%s: '%s' Precache can only be done in spawn functions", __func__, s); From de3679f0391f1452532c820f07a8c4042b1c4281 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 20 Sep 2023 20:20:14 +0700 Subject: [PATCH 07/30] Host_Motd_f: Fixed viewing motd when motdfile is not specified --- rehlds/engine/common.cpp | 28 ++++++++++++++++++++++++++++ rehlds/engine/common.h | 2 ++ rehlds/engine/host_cmd.cpp | 10 +++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rehlds/engine/common.cpp b/rehlds/engine/common.cpp index badf9931b..e70d03ca1 100644 --- a/rehlds/engine/common.cpp +++ b/rehlds/engine/common.cpp @@ -1978,6 +1978,34 @@ NOXREF int COM_ExpandFilename(char *filename) return *filename != 0; } +// small helper function shared by lots of modules +qboolean COM_IsAbsolutePath(const char *pStr) +{ + if (strchr(pStr, ':') || pStr[0] == '/' || pStr[0] == '\\') + return FALSE; + + return TRUE; +} + +qboolean COM_IsValidPath(const char *pszFilename) +{ + if (!pszFilename) + return FALSE; + + if (Q_strlen(pszFilename) <= 0 || + Q_strstr(pszFilename, "\\\\") || // to protect network paths + Q_strstr(pszFilename, ":") || // to protect absolute paths + Q_strstr(pszFilename, "..") || // to protect relative paths + Q_strstr(pszFilename, "~") || + Q_strstr(pszFilename, "\n") || // CFileSystem_Stdio::FS_fopen doesn't allow this + Q_strstr(pszFilename, "\r")) // CFileSystem_Stdio::FS_fopen doesn't allow this + { + return FALSE; + } + + return TRUE; +} + int EXT_FUNC COM_FileSize(const char *filename) { FileHandle_t fp; diff --git a/rehlds/engine/common.h b/rehlds/engine/common.h index 1a7fc081a..94d940480 100644 --- a/rehlds/engine/common.h +++ b/rehlds/engine/common.h @@ -187,6 +187,8 @@ void COM_CreatePath(char *path); NOXREF void COM_CopyFile(char *netpath, char *cachepath); NOXREF int COM_ExpandFilename(char *filename); int COM_FileSize(const char *filename); +qboolean COM_IsAbsolutePath(const char *pStr); +qboolean COM_IsValidPath(const char *pszFilename); unsigned char *COM_LoadFile(const char *path, int usehunk, int *pLength); void COM_FreeFile(void *buffer); void COM_CopyFileChunk(FileHandle_t dst, FileHandle_t src, int nSize); diff --git a/rehlds/engine/host_cmd.cpp b/rehlds/engine/host_cmd.cpp index d76815cb8..3d6558c2b 100644 --- a/rehlds/engine/host_cmd.cpp +++ b/rehlds/engine/host_cmd.cpp @@ -205,11 +205,19 @@ void Host_Motd_f(void) char *next; pFileList = motdfile.string; - if (*pFileList == '/' || Q_strstr(pFileList, ":") || Q_strstr(pFileList, "..") || Q_strstr(pFileList, "\\")) + if (!COM_IsValidPath(pFileList) || COM_IsAbsolutePath(pFileList)) { Con_Printf("Unable to open %s (contains illegal characters)\n", pFileList); return; } + + const char *pchExtension = COM_FileExtension(pFileList); + if (Q_stricmp(pchExtension, "txt") != 0) + { + Con_Printf("Invalid motdfile name %s (wrong file extension, must be .txt)\n", pFileList); + return; + } + pFile = FS_Open(pFileList, "rb"); if (!pFile) { From 6f031901cfa85d86c6028086bc9335db1e867a03 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 25 Sep 2023 02:08:04 +0700 Subject: [PATCH 08/30] Added cvarhook from latest HLDS build Making mapcyclefile/sv_cheats work in realtime Resolve #868 --- rehlds/common/cvardef.h | 12 ++++++++ rehlds/engine/cvar.cpp | 49 +++++++++++++++++++++++++++++-- rehlds/engine/host.cpp | 31 +++++++++++++++++-- rehlds/engine/net_ws.cpp | 4 +-- rehlds/engine/server.h | 1 - rehlds/engine/sv_main.cpp | 48 ++++++++++++++++++++++++++++-- rehlds/engine/zone.cpp | 2 +- rehlds/msvc/ReHLDS.vcxproj | 1 + rehlds/rehlds/rehlds_api_impl.cpp | 4 ++- 9 files changed, 140 insertions(+), 12 deletions(-) diff --git a/rehlds/common/cvardef.h b/rehlds/common/cvardef.h index aea0f9e03..5d9e45ec4 100644 --- a/rehlds/common/cvardef.h +++ b/rehlds/common/cvardef.h @@ -47,4 +47,16 @@ struct cvar_listener_t const char *name; }; +typedef void (*pfnCvar_HookVariable_t) (cvar_t *pCvar); + +struct cvarhook_t +{ + pfnCvar_HookVariable_t hook; + + cvar_t *cvar; + cvarhook_t *next; +}; + +qboolean Cvar_HookVariable(const char *var_name, cvarhook_t *pHook); + #endif // CVARDEF_H diff --git a/rehlds/engine/cvar.cpp b/rehlds/engine/cvar.cpp index 9285fa254..7c7ce2b7f 100644 --- a/rehlds/engine/cvar.cpp +++ b/rehlds/engine/cvar.cpp @@ -32,7 +32,8 @@ All cvar names are case insensitive! Values not. */ -cvar_t *cvar_vars; +cvar_t *cvar_vars = NULL; +cvarhook_t *cvar_hooks = NULL; char cvar_null_string[] = ""; void Cvar_Init(void) @@ -319,8 +320,10 @@ void Cvar_DirectSet(struct cvar_s *var, const char *value) void Cvar_Set(const char *var_name, const char *value) { - cvar_t *var = Cvar_FindVar(var_name); + cvar_t *var; + cvarhook_t *pHook; + var = Cvar_FindVar(var_name); if (!var) { Con_DPrintf("%s: variable \"%s\" not found\n", __func__, var_name); @@ -328,6 +331,15 @@ void Cvar_Set(const char *var_name, const char *value) } Cvar_DirectSet(var, value); + + for (pHook = cvar_hooks; pHook; pHook = pHook->next) + { + if (pHook->cvar == var) + { + pHook->hook(var); + break; + } + } } void Cvar_SetValue(const char *var_name, float value) @@ -730,3 +742,36 @@ void Cvar_CmdInit(void) { Cmd_AddCommand("cvarlist", Cmd_CvarList_f); } + +qboolean Cvar_HookVariable(const char *var_name, cvarhook_t *pHook) +{ + cvar_t *cvar; + + if (!pHook || !pHook->hook) + return FALSE; + + if (pHook->cvar || pHook->next) + return FALSE; + + cvar = Cvar_FindVar(var_name); + if (!cvar) + return FALSE; + + cvarhook_t *pCur = cvar_hooks; + pHook->cvar = cvar; + + if (pCur) + { + while (pCur->next) + pCur = pCur->next; + + pCur->next = pHook; + } + else + { + // First in chain is null, assign pHook to it + cvar_hooks = pHook; + } + + return TRUE; +} diff --git a/rehlds/engine/host.cpp b/rehlds/engine/host.cpp index 3416efdf7..31560b71f 100644 --- a/rehlds/engine/host.cpp +++ b/rehlds/engine/host.cpp @@ -57,7 +57,11 @@ cvar_t deathmatch = { "deathmatch", "0", FCVAR_SERVER, 0.0f, NULL }; cvar_t coop = { "coop", "0", FCVAR_SERVER, 0.0f, NULL }; cvar_t sys_ticrate = { "sys_ticrate", "100.0", 0, 0.0f, NULL }; + +void sys_timescale_hook_callback(cvar_t *cvar); +cvarhook_t sys_timescale_hook = { sys_timescale_hook_callback, NULL, NULL }; cvar_t sys_timescale = { "sys_timescale", "1.0", 0, 0.0f, NULL }; + cvar_t fps_max = { "fps_max", "100.0", FCVAR_ARCHIVE, 0.0f, NULL }; cvar_t host_killtime = { "host_killtime", "0.0", 0, 0.0f, NULL }; cvar_t sv_stats = { "sv_stats", "1", 0, 0.0f, NULL }; @@ -143,6 +147,9 @@ void Host_InitLocal(void) Host_InitCommands(); Cvar_RegisterVariable(&host_killtime); Cvar_RegisterVariable(&sys_ticrate); + Cvar_RegisterVariable(&sys_timescale); + Cvar_HookVariable(sys_timescale.name, &sys_timescale_hook); + Cvar_RegisterVariable(&fps_max); Cvar_RegisterVariable(&fps_override); Cvar_RegisterVariable(&host_name); @@ -355,7 +362,7 @@ void SV_ClientPrintf(const char *fmt, ...) { va_list va; char string[1024]; - + va_start(va, fmt); Q_vsnprintf(string, ARRAYSIZE(string) - 1, fmt, va); va_end(va); @@ -367,7 +374,7 @@ void SV_ClientPrintf(const char *fmt, ...) void EXT_FUNC SV_ClientPrintf_internal(const char *Dest) { char string[1024]; - + Q_strlcpy(string, Dest, min(strlen(Dest) + 1, sizeof(string))); MSG_WriteByte(&host_client->netchan.message, svc_print); MSG_WriteString(&host_client->netchan.message, string); @@ -1275,3 +1282,23 @@ void Host_Shutdown(void) g_psv.time = 0.0f; g_pcl.time = 0.0f; } + +void sys_timescale_hook_callback(cvar_t *cvar) +{ + int i; + client_t *client = NULL; + + if (!Host_IsServerActive()) + return; + + for (i = 0; i < g_psvs.maxclients; i++) + { + client = &g_psvs.clients[i]; + + if (!client->fakeclient && (client->active || client->spawned || client->connected)) + { + MSG_WriteByte(&client->netchan.message, svc_timescale); + MSG_WriteFloat(&client->netchan.message, max(0.1f, sys_timescale.value)); + } + } +} diff --git a/rehlds/engine/net_ws.cpp b/rehlds/engine/net_ws.cpp index 219b73a07..4880da9b7 100644 --- a/rehlds/engine/net_ws.cpp +++ b/rehlds/engine/net_ws.cpp @@ -651,7 +651,7 @@ void NET_AdjustLag() } lasttime = realtime; - if (allow_cheats || fakelag.value == 0.0) + if (sv_cheats.value || fakelag.value == 0.0) { if (fakelag.value != gFakeLag) { @@ -689,7 +689,7 @@ qboolean NET_LagPacket(qboolean newdata, netsrc_t sock, netadr_t *from, sizebuf_ { if (fakeloss.value != 0.0) { - if (allow_cheats) + if (sv_cheats.value) { static int losscount[NS_MAX] = {}; ++losscount[sock]; diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h index 4bdd7efb6..eedb6f076 100644 --- a/rehlds/engine/server.h +++ b/rehlds/engine/server.h @@ -349,7 +349,6 @@ extern cvar_t sv_proxies; extern cvar_t sv_outofdatetime; extern cvar_t mapchangecfgfile; -extern qboolean allow_cheats; extern cvar_t mp_logecho; extern cvar_t mp_logfile; extern cvar_t sv_allow_download; diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index fa79fe9c5..b3779c959 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -100,7 +100,6 @@ redirect_t sv_redirected; netadr_t sv_redirectto; GameType_e g_eGameType = GT_Unitialized; -qboolean allow_cheats; char *gNullString = ""; int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; @@ -130,6 +129,12 @@ cvar_t sv_waterfriction = { "sv_waterfriction", "1", FCVAR_SERVER, 0.0f, NULL }; cvar_t sv_zmax = { "sv_zmax", "4096", FCVAR_SPONLY, 0.0f, NULL }; cvar_t sv_wateramp = { "sv_wateramp", "0", 0, 0.0f, NULL }; +void sv_cheats_hook_callback(cvar_t *cvar); +void mapcyclefile_hook_callback(cvar_t *cvar); + +cvarhook_t sv_cheats_hook = { sv_cheats_hook_callback, NULL, NULL }; +cvarhook_t mapcyclefile_hook = { mapcyclefile_hook_callback, NULL, NULL }; + cvar_t sv_skyname = { "sv_skyname", "desert", 0, 0.0f, NULL }; cvar_t mapcyclefile = { "mapcyclefile", "mapcycle.txt", 0, 0.0f, NULL }; cvar_t motdfile = { "motdfile", "motd.txt", 0, 0.0f, NULL }; @@ -1173,7 +1178,7 @@ void SV_SendServerinfo_internal(sizebuf_t *msg, client_t *client) MSG_WriteByte(msg, svc_sendextrainfo); MSG_WriteString(msg, com_clientfallback); - MSG_WriteByte(msg, allow_cheats); + MSG_WriteByte(msg, sv_cheats.value != 0); SV_WriteDeltaDescriptionsToClient(msg); SV_SetMoveVars(); @@ -6257,7 +6262,6 @@ int SV_SpawnServer(qboolean bIsDemo, char *server, char *startspot) gGlobalVariables.serverflags = g_psvs.serverflags; gGlobalVariables.mapname = (size_t)g_psv.name - (size_t)pr_strings; gGlobalVariables.startspot = (size_t)g_psv.startspot - (size_t)pr_strings; - allow_cheats = sv_cheats.value; SV_SetMoveVars(); return 1; @@ -6576,6 +6580,42 @@ void EXT_FUNC SV_SerializeSteamid(USERID_t* id, USERID_t* serialized) *serialized = *id; } +void sv_cheats_hook_callback(cvar_t *cvar) +{ + int i; + client_t *client = NULL; + + if (!Host_IsServerActive()) + return; + + for (i = 0; i < g_psvs.maxclients; i++) + { + client = &g_psvs.clients[i]; + + if (!client->fakeclient && (client->active || client->spawned || client->connected)) + { + MSG_WriteByte(&client->netchan.message, svc_sendextrainfo); + MSG_WriteString(&client->netchan.message, ""); + MSG_WriteByte(&client->netchan.message, sv_cheats.value != 0); + } + } +} + +void mapcyclefile_hook_callback(cvar_t *cvar) +{ + char buf[MAX_PATH + 4]; + + if (!Q_strcmp(COM_FileExtension(cvar->string), "txt")) + return; + + Q_snprintf(buf, sizeof(buf) - 3, "%s.txt", cvar->string); + + if (!Q_strcmp(COM_FileExtension(buf), "txt")) + Cvar_DirectSet(cvar, buf); + else + Cvar_DirectSet(cvar, "mapcycle.txt"); +} + void SV_BanId_f(void) { char szreason[256]; @@ -8014,6 +8054,7 @@ void SV_Init(void) Cvar_RegisterVariable(&sv_skyname); Cvar_RegisterVariable(&sv_maxvelocity); Cvar_RegisterVariable(&sv_cheats); + Cvar_HookVariable(sv_cheats.name, &sv_cheats_hook); if (COM_CheckParm("-dev")) Cvar_SetValue("sv_cheats", 1.0); Cvar_RegisterVariable(&sv_spectatormaxspeed); @@ -8025,6 +8066,7 @@ void SV_Init(void) Cvar_RegisterVariable(&sv_logbans); Cvar_RegisterVariable(&hpk_maxsize); Cvar_RegisterVariable(&mapcyclefile); + Cvar_HookVariable(mapcyclefile.name, &mapcyclefile_hook); Cvar_RegisterVariable(&motdfile); Cvar_RegisterVariable(&servercfgfile); Cvar_RegisterVariable(&mapchangecfgfile); diff --git a/rehlds/engine/zone.cpp b/rehlds/engine/zone.cpp index afd84697a..3a1ab65f7 100644 --- a/rehlds/engine/zone.cpp +++ b/rehlds/engine/zone.cpp @@ -679,7 +679,7 @@ void Cache_Force_Flush() void Cache_Flush() { - if (g_pcl.maxclients <= 1 || allow_cheats) + if (g_pcl.maxclients <= 1 || sv_cheats.value) { Cache_Force_Flush(); } diff --git a/rehlds/msvc/ReHLDS.vcxproj b/rehlds/msvc/ReHLDS.vcxproj index 2078e8301..dc153551e 100644 --- a/rehlds/msvc/ReHLDS.vcxproj +++ b/rehlds/msvc/ReHLDS.vcxproj @@ -463,6 +463,7 @@ {6973dca5-253c-4d84-b51e-187e035eae06} + false diff --git a/rehlds/rehlds/rehlds_api_impl.cpp b/rehlds/rehlds/rehlds_api_impl.cpp index d18622036..8d7f9dad6 100644 --- a/rehlds/rehlds/rehlds_api_impl.cpp +++ b/rehlds/rehlds/rehlds_api_impl.cpp @@ -52,7 +52,9 @@ char* EXT_FUNC GetClientFallback_api() { } int* EXT_FUNC GetAllowCheats_api() { - return &allow_cheats; + static int sv_cheats_stub = 0; + Con_Printf("WARNING! allow_cheats marked as deprecated! Use sv_cheats cvar directly!\n"); + return &sv_cheats_stub; } bool EXT_FUNC GSBSecure_api() { From 5ec8c29185a960ae31b090de735f8399a16b3042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Mu=C3=B1oz?= Date: Sun, 24 Sep 2023 17:40:50 -0300 Subject: [PATCH 09/30] Update world.cpp (#985) --- rehlds/engine/world.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rehlds/engine/world.cpp b/rehlds/engine/world.cpp index 4cec668ff..2ba99d0af 100644 --- a/rehlds/engine/world.cpp +++ b/rehlds/engine/world.cpp @@ -1190,13 +1190,15 @@ void SV_ClipToLinks(areanode_t *node, moveclip_t *clip) if (touch->v.solid == SOLID_TRIGGER) Sys_Error("%s: Trigger in clipping list", __func__); +#ifndef REHLDS_OPT_PEDANTIC if (gNewDLLFunctions.pfnShouldCollide && !gNewDLLFunctions.pfnShouldCollide(touch, clip->passedict)) #ifdef REHLDS_FIXES // https://github.com/dreamstalker/rehlds/issues/46 continue; #else return; -#endif +#endif // REHLDS_FIXES +#endif // REHLDS_OPT_PEDANTIC // monsterclip filter if (touch->v.solid == SOLID_BSP) @@ -1248,6 +1250,16 @@ void SV_ClipToLinks(areanode_t *node, moveclip_t *clip) continue; // don't clip against owner } +#ifdef REHLDS_OPT_PEDANTIC + if (gNewDLLFunctions.pfnShouldCollide && !gNewDLLFunctions.pfnShouldCollide(touch, clip->passedict)) +#ifdef REHLDS_FIXES + // https://github.com/dreamstalker/rehlds/issues/46 + continue; +#else + return; +#endif // REHLDS_FIXES +#endif // REHLDS_OPT_PEDANTIC + trace_t trace; if (touch->v.flags & FL_MONSTER) trace = SV_ClipMoveToEntity(touch, clip->start, clip->mins2, clip->maxs2, clip->end); From b7f6eb8023c87bc625317d608a849b3ea94f9ec6 Mon Sep 17 00:00:00 2001 From: Hamdi Date: Wed, 27 Sep 2023 18:41:37 +0100 Subject: [PATCH 10/30] Minor refactor ( add BoundsIntersect function ) (#986) --- rehlds/engine/mathlib.cpp | 9 +++++++++ rehlds/engine/mathlib_e.h | 1 + rehlds/engine/sv_user.cpp | 9 +-------- rehlds/engine/world.cpp | 28 ++++------------------------ 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/rehlds/engine/mathlib.cpp b/rehlds/engine/mathlib.cpp index 4647b9722..667f510e1 100644 --- a/rehlds/engine/mathlib.cpp +++ b/rehlds/engine/mathlib.cpp @@ -418,3 +418,12 @@ qboolean VectorCompare(const vec_t *v1, const vec_t *v2) } #endif // #if !defined(REHLDS_SSE) + +qboolean BoundsIntersect(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2) +{ + if (mins1[0] > maxs2[0] || mins1[1] > maxs2[1] || mins1[2] > maxs2[2]) + return FALSE; + if (maxs1[0] < mins2[0] || maxs1[1] < mins2[1] || maxs1[2] < mins2[2]) + return FALSE; + return TRUE; +} diff --git a/rehlds/engine/mathlib_e.h b/rehlds/engine/mathlib_e.h index 3b265a10f..921c701ed 100644 --- a/rehlds/engine/mathlib_e.h +++ b/rehlds/engine/mathlib_e.h @@ -171,3 +171,4 @@ void R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4]); NOBODY void FloorDivMod(double numer, double denom, int *quotient, int *rem); NOBODY int GreatestCommonDivisor(int i1, int i2); NOBODY fixed16_t Invert24To16(fixed16_t val); +qboolean BoundsIntersect(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); diff --git a/rehlds/engine/sv_user.cpp b/rehlds/engine/sv_user.cpp index 0912cc089..f4c077d59 100644 --- a/rehlds/engine/sv_user.cpp +++ b/rehlds/engine/sv_user.cpp @@ -525,7 +525,6 @@ void SV_AddLinksToPM_(areanode_t *node, float *pmove_mins, float *pmove_maxs) edict_t *check; int e; physent_t *ve; - int i; link_t *next; float *fmax; float *fmin; @@ -588,13 +587,7 @@ void SV_AddLinksToPM_(areanode_t *node, float *pmove_mins, float *pmove_maxs) if (check->v.flags & FL_CLIENT) SV_GetTrueMinMax(e - 1, &fmin, &fmax); - for (i = 0; i < 3; i++) - { - if (fmin[i] > pmove_maxs[i] || fmax[i] < pmove_mins[i]) - break; - } - - if (i != 3) + if (!BoundsIntersect(pmove_mins, pmove_maxs, fmin, fmax)) continue; if (check->v.solid || check->v.skin != -16) diff --git a/rehlds/engine/world.cpp b/rehlds/engine/world.cpp index 2ba99d0af..8038ab755 100644 --- a/rehlds/engine/world.cpp +++ b/rehlds/engine/world.cpp @@ -359,12 +359,7 @@ void SV_TouchLinks(edict_t *ent, areanode_t *node) if (touch->v.solid != SOLID_TRIGGER) continue; - if (ent->v.absmin[0] > touch->v.absmax[0] - || ent->v.absmin[1] > touch->v.absmax[1] - || ent->v.absmin[2] > touch->v.absmax[2] - || ent->v.absmax[0] < touch->v.absmin[0] - || ent->v.absmax[1] < touch->v.absmin[1] - || ent->v.absmax[2] < touch->v.absmin[2]) + if (!BoundsIntersect(ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax)) continue; // check brush triggers accuracy @@ -647,12 +642,7 @@ int SV_LinkContents(areanode_t *node, const vec_t *pos) if (Mod_GetType(touch->v.modelindex) != mod_brush) continue; - if (pos[0] > touch->v.absmax[0] - || pos[1] > touch->v.absmax[1] - || pos[2] > touch->v.absmax[2] - || pos[0] < touch->v.absmin[0] - || pos[1] < touch->v.absmin[1] - || pos[2] < touch->v.absmin[2]) + if (!BoundsIntersect(pos, pos, touch->v.absmin, touch->v.absmax)) continue; int contents = touch->v.skin; @@ -1216,12 +1206,7 @@ void SV_ClipToLinks(areanode_t *node, moveclip_t *clip) if (clip->ignoretrans && touch->v.rendermode != kRenderNormal && !(touch->v.flags & FL_WORLDBRUSH)) continue; - if (clip->boxmins[0] > touch->v.absmax[0] - || clip->boxmins[1] > touch->v.absmax[1] - || clip->boxmins[2] > touch->v.absmax[2] - || clip->boxmaxs[0] < touch->v.absmin[0] - || clip->boxmaxs[1] < touch->v.absmin[1] - || clip->boxmaxs[2] < touch->v.absmin[2]) + if (!BoundsIntersect(clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax)) continue; if (touch->v.solid != SOLID_SLIDEBOX @@ -1310,12 +1295,7 @@ void SV_ClipToWorldbrush(areanode_t *node, moveclip_t *clip) if (!(touch->v.flags & FL_WORLDBRUSH)) continue; - if (clip->boxmins[0] > touch->v.absmax[0] - || clip->boxmins[1] > touch->v.absmax[1] - || clip->boxmins[2] > touch->v.absmax[2] - || clip->boxmaxs[0] < touch->v.absmin[0] - || clip->boxmaxs[1] < touch->v.absmin[1] - || clip->boxmaxs[2] < touch->v.absmin[2]) + if (!BoundsIntersect(clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax)) continue; if (clip->trace.allsolid) From 7fcec97af4c6598ab6a65d60cf424b7775a11729 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 9 Oct 2023 01:57:11 +0700 Subject: [PATCH 11/30] Add support cheats command god,notarget,noclip --- rehlds/engine/host_cmd.cpp | 110 ++++++++++++++++++++++++++++++++++++- rehlds/engine/sv_user.cpp | 2 +- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/rehlds/engine/host_cmd.cpp b/rehlds/engine/host_cmd.cpp index 3d6558c2b..993e56abd 100644 --- a/rehlds/engine/host_cmd.cpp +++ b/rehlds/engine/host_cmd.cpp @@ -761,6 +761,111 @@ void Host_Status_Formatted_f(void) Host_Status_Printf(conprint, log, "%i users\n", nClients); } +// Sets client to godmode +void Host_God_f(void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer(); + return; + } + + if (!sv_cheats.value) + return; + + sv_player->v.flags = (int)sv_player->v.flags ^ FL_GODMODE; + if (!((int)sv_player->v.flags & FL_GODMODE)) + SV_ClientPrintf("godmode OFF\n"); + else + SV_ClientPrintf("godmode ON\n"); +} + +// Sets client to notarget mode +void Host_Notarget_f(void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer(); + return; + } + + if (!sv_cheats.value) + return; + + sv_player->v.flags = (int)sv_player->v.flags ^ FL_NOTARGET; + if (!((int)sv_player->v.flags & FL_NOTARGET)) + SV_ClientPrintf("notarget OFF\n"); + else + SV_ClientPrintf("notarget ON\n"); +} + +// Searches along the direction ray in steps of "step" to see if +// the entity position is passible +// Used for putting the player in valid space when toggling off noclip mode +int FindPassableSpace(edict_t *pEdict, vec_t *direction, float step) +{ + int i; + + for (i = 0; i < 100; i++) + { + VectorMA(pEdict->v.origin, step, direction, pEdict->v.origin); + + if (!SV_TestEntityPosition(pEdict)) + { + // Store old origin + VectorCopy(pEdict->v.origin, pEdict->v.oldorigin); + return TRUE; + } + } + + return FALSE; +} + +void Host_Noclip_f(void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer(); + return; + } + + if (!sv_cheats.value) + return; + + if (sv_player->v.movetype != MOVETYPE_NOCLIP) + { + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf("noclip ON\n"); + } + else + { + sv_player->v.movetype = MOVETYPE_WALK; + + // Store old origin + VectorCopy(sv_player->v.origin, sv_player->v.oldorigin); + + SV_ClientPrintf("noclip OFF\n"); + + if (SV_TestEntityPosition(sv_player)) + { + vec3_t forward, right, up; + AngleVectors(sv_player->v.v_angle, forward, right, up); + + if (!FindPassableSpace(sv_player, forward, 1.0) + && !FindPassableSpace(sv_player, right, 1.0) + && !FindPassableSpace(sv_player, right, -1.0) // left + && !FindPassableSpace(sv_player, up, 1.0) // up + && !FindPassableSpace(sv_player, up, -1.0) // down + && !FindPassableSpace(sv_player, forward, -1.0)) // back + { + Con_DPrintf("Can't find the world\n"); + } + + VectorCopy(sv_player->v.oldorigin, sv_player->v.origin); + } + } +} + void Host_Ping_f(void) { int i; @@ -3146,11 +3251,12 @@ void Host_InitCommands(void) Cmd_AddCommand("setinfo", Host_SetInfo_f); Cmd_AddCommand("fullinfo", Host_FullInfo_f); -#ifndef SWDS Cmd_AddCommand("god", Host_God_f); Cmd_AddCommand("notarget", Host_Notarget_f); - Cmd_AddCommand("fly", Host_Fly_f); Cmd_AddCommand("noclip", Host_Noclip_f); + +#ifndef SWDS + Cmd_AddCommand("fly", Host_Fly_f); Cmd_AddCommand("viewmodel", Host_Viewmodel_f); Cmd_AddCommand("viewframe", Host_Viewframe_f); Cmd_AddCommand("viewnext", Host_Viewnext_f); diff --git a/rehlds/engine/sv_user.cpp b/rehlds/engine/sv_user.cpp index f4c077d59..34ec65176 100644 --- a/rehlds/engine/sv_user.cpp +++ b/rehlds/engine/sv_user.cpp @@ -42,7 +42,7 @@ edict_t *sv_player; qboolean nofind; #if defined(SWDS) && defined(REHLDS_FIXES) -const char *clcommands[] = { "status", "name", "kill", "pause", "spawn", "new", "sendres", "dropclient", "kick", "ping", "dlfile", "setinfo", "sendents", "fullupdate", "setpause", "unpause", NULL }; +const char *clcommands[] = { "status", "name", "kill", "pause", "spawn", "new", "sendres", "dropclient", "kick", "ping", "dlfile", "setinfo", "sendents", "fullupdate", "setpause", "unpause", "noclip", "god", "notarget", NULL }; #else const char *clcommands[23] = { "status", "god", "notarget", "fly", "name", "noclip", "kill", "pause", "spawn", "new", "sendres", "dropclient", "kick", "ping", "dlfile", "nextdl", "setinfo", "showinfo", "sendents", "fullupdate", "setpause", "unpause", NULL }; #endif From 3f19bc1d3e254d9665893e93383487ba03d29f2a Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Tue, 21 Nov 2023 04:35:36 +0300 Subject: [PATCH 12/30] engine: add sv_allow_autoaim cvar for HL25 DLL compatibility (#1000) add sv_allow_autoaim cvar for HL25 DLL compatibility --- rehlds/engine/pr_cmds.cpp | 9 ++++++++- rehlds/engine/server.h | 1 + rehlds/engine/sv_main.cpp | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rehlds/engine/pr_cmds.cpp b/rehlds/engine/pr_cmds.cpp index e83cbb429..dfd284683 100644 --- a/rehlds/engine/pr_cmds.cpp +++ b/rehlds/engine/pr_cmds.cpp @@ -1802,7 +1802,14 @@ void EXT_FUNC PF_aim_I(edict_t *ent, float speed, float *rgflReturn) bestdir[1] = dir[1]; bestdir[2] = dir[2]; bestdir[0] = dir[0]; - bestdist = sv_aim.value; + if (sv_allow_autoaim.value) + { + bestdist = sv_aim.value; + } + else + { + bestdist = 0.0f; + } for (int i = 1; i < g_psv.num_edicts; i++) { diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h index eedb6f076..5fda2c1ba 100644 --- a/rehlds/engine/server.h +++ b/rehlds/engine/server.h @@ -283,6 +283,7 @@ extern rehlds_server_t g_rehlds_sv; extern cvar_t sv_lan; extern cvar_t sv_lan_rate; extern cvar_t sv_aim; +extern cvar_t sv_allow_autoaim; extern cvar_t sv_skycolor_r; extern cvar_t sv_skycolor_g; diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index b3779c959..640cb2a45 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -114,6 +114,7 @@ int giNextUserMsg = 64; cvar_t sv_lan = { "sv_lan", "0", 0, 0.0f, NULL }; cvar_t sv_lan_rate = { "sv_lan_rate", "20000.0", 0, 0.0f, NULL }; cvar_t sv_aim = { "sv_aim", "1", FCVAR_SERVER | FCVAR_ARCHIVE , 0.0f, NULL }; +cvar_t sv_allow_autoaim = { "sv_allow_autoaim", "1", FCVAR_SERVER | FCVAR_ARCHIVE, 0.0f, NULL }; cvar_t sv_skycolor_r = { "sv_skycolor_r", "0", 0, 0.0f, NULL }; cvar_t sv_skycolor_g = { "sv_skycolor_g", "0", 0, 0.0f, NULL }; @@ -8024,6 +8025,9 @@ void SV_Init(void) Cvar_RegisterVariable(&sv_visiblemaxplayers); Cvar_RegisterVariable(&sv_password); Cvar_RegisterVariable(&sv_aim); +#ifdef REHLDS_FIXES + Cvar_RegisterVariable(&sv_allow_autoaim); +#endif Cvar_RegisterVariable(&violence_hblood); Cvar_RegisterVariable(&violence_ablood); Cvar_RegisterVariable(&violence_hgibs); From ffb65795dd4c9ff9b6e839e3f65a51ca9a881395 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 22 Nov 2023 23:41:21 +0700 Subject: [PATCH 13/30] Update workflows/build.yml Fixes #1004 --- .github/workflows/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29a64b6d7..d76a2c63f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,12 +25,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1.0.2 + uses: microsoft/setup-msbuild@v1.1.3 - name: Build and Run unittests run: | @@ -70,7 +70,7 @@ jobs: move msvc\${{ env.buildRelease }}\director.pdb publish\debug\director.pdb - name: Deploy artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 with: name: win32 path: publish/* @@ -92,7 +92,7 @@ jobs: steps: - name: Deploying windows artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: win32 @@ -155,7 +155,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -228,7 +228,7 @@ jobs: shell: bash - name: Deploy artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3.1.1 id: upload-job with: name: linux32 @@ -247,12 +247,12 @@ jobs: steps: - name: Deploying linux artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: linux32 - name: Deploying windows artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: win32 From 0af97d98bbe31af26395ba616d4be6a089f691b6 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Fri, 24 Nov 2023 22:47:08 +0700 Subject: [PATCH 14/30] Add reg cvar r_cachestudio --- rehlds/engine/host.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rehlds/engine/host.cpp b/rehlds/engine/host.cpp index 31560b71f..673b3297f 100644 --- a/rehlds/engine/host.cpp +++ b/rehlds/engine/host.cpp @@ -1197,7 +1197,11 @@ int Host_Init(quakeparms_t *parms) else { Cvar_RegisterVariable(&suitvolume); +#ifdef REHLDS_FIXES + Cvar_RegisterVariable(&r_cachestudio); +#endif } + Cbuf_InsertText("exec valve.rc\n"); Hunk_AllocName(0, "-HOST_HUNKLEVEL-"); host_hunklevel = Hunk_LowMark(); From 93f5775ac26240782981f47ee8e052fb53d30877 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 14 Dec 2023 07:30:29 +0700 Subject: [PATCH 15/30] Reworked AlertMessage --- rehlds/engine/sys_dll.cpp | 63 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/rehlds/engine/sys_dll.cpp b/rehlds/engine/sys_dll.cpp index 293119d22..08431a490 100644 --- a/rehlds/engine/sys_dll.cpp +++ b/rehlds/engine/sys_dll.cpp @@ -1150,46 +1150,47 @@ void EXT_FUNC EngineFprintf(void *pfile, const char *szFmt, ...) void EXT_FUNC AlertMessage(ALERT_TYPE atype, const char *szFmt, ...) { + char szOut[2048]; va_list argptr; - static char szOut[1024]; - va_start(argptr, szFmt); if (atype == at_logged && g_psvs.maxclients > 1) { + va_start(argptr, szFmt); Q_vsnprintf(szOut, sizeof(szOut), szFmt, argptr); + va_end(argptr); + Log_Printf("%s", szOut); + return; } - else if (developer.value != 0.0f) + + if (!developer.value) + return; + + if (atype == at_aiconsole && developer.value < 2) + return; + + va_start(argptr, szFmt); + Q_vsnprintf(szOut, sizeof(szOut), szFmt, argptr); + va_end(argptr); + + switch (atype) { - switch (atype) - { - case at_notice: - Q_strcpy(szOut, "NOTE: "); - break; - case at_console: - szOut[0] = 0; - break; - case at_aiconsole: - if (developer.value < 2.0f) - return; - szOut[0] = 0; - break; - case at_warning: - Q_strcpy(szOut, "WARNING: "); - break; - case at_error: - Q_strcpy(szOut, "ERROR: "); - break; - case at_logged: - break; - default: - break; - } - int iLen = Q_strlen(szOut); - Q_vsnprintf(&szOut[iLen], sizeof(szOut) - iLen, szFmt, argptr); + case at_notice: + Con_Printf("NOTE: %s", szOut); + break; + case at_console: + case at_aiconsole: Con_Printf("%s", szOut); + break; + case at_warning: + Con_Printf("WARNING: %s", szOut); + break; + case at_error: + Con_Printf("ERROR: %s", szOut); + break; + default: + break; } - va_end(argptr); } NOXREF void Sys_SplitPath(const char *path, char *drive, char *dir, char *fname, char *ext) @@ -1326,7 +1327,7 @@ void Con_Printf(const char *fmt, ...) va_start(va, fmt); Q_vsnprintf(Dest, sizeof(Dest), fmt, va); va_end(va); - + g_RehldsHookchains.m_Con_Printf.callChain(Con_Printf_internal, Dest); } From 32857e77859789275daeb353a278cd6632c2b6bf Mon Sep 17 00:00:00 2001 From: s1lentq Date: Fri, 12 Jan 2024 22:32:15 +0700 Subject: [PATCH 16/30] Fix no exec config file when exceed limit text buffer --- rehlds/engine/cmd.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/rehlds/engine/cmd.cpp b/rehlds/engine/cmd.cpp index 7dcf76c43..ae541553a 100644 --- a/rehlds/engine/cmd.cpp +++ b/rehlds/engine/cmd.cpp @@ -235,7 +235,8 @@ void Cbuf_Execute(void) } // execute the command line - Cmd_ExecuteString(line, src_command); + if (len > 0) + Cmd_ExecuteString(line, src_command); if (cmd_wait) { @@ -406,17 +407,12 @@ void Cmd_Exec_f(void) else { char *pszDataPtr = configContents; - while (true) + while (pszDataPtr && *pszDataPtr) { Cbuf_Execute(); // TODO: This doesn't obey the rule to first execute commands from the file, and then the others in the buffer pszDataPtr = COM_ParseLine(pszDataPtr); // TODO: COM_ParseLine can be const char* - - if (com_token[0] == 0) - { - break; - } - - Cbuf_InsertTextLines(com_token); + if (com_token[0]) + Cbuf_InsertTextLines(com_token); } } From 5002ff9abe86e9573952e2dc45ec9229d0044a3b Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 14 Jan 2024 00:18:08 +0700 Subject: [PATCH 17/30] Prevent crash "Cache_UnlinkLRU: NULL link" on client-side if aiment with sprite model will be to render as a studio model --- rehlds/engine/sv_main.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 640cb2a45..6d0387d0b 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -4698,11 +4698,25 @@ void SV_WriteEntitiesToClient(client_t *client, sizebuf_t *msg) auto &entityState = curPack->entities[i]; if (entityState.number > MAX_CLIENTS) { - if (sv_rehlds_attachedentities_playeranimationspeed_fix.string[0] == '1' - && entityState.movetype == MOVETYPE_FOLLOW - && 1 <= entityState.aiment && entityState.aiment <= MAX_CLIENTS) + if (entityState.movetype == MOVETYPE_FOLLOW && entityState.aiment > 0) { - attachedEntCount[entityState.aiment]++; + if (sv_rehlds_attachedentities_playeranimationspeed_fix.string[0] == '1' && + entityState.aiment <= MAX_CLIENTS) + { + attachedEntCount[entityState.aiment]++; + } + + // Prevent crash "Cache_UnlinkLRU: NULL link" on client-side + // if aiment with sprite model will be to render as a studio model + if (entityState.aiment < g_psv.num_edicts) + { + edict_t *ent = &g_psv.edicts[entityState.aiment]; + if ((ent->v.modelindex >= 0 && ent->v.modelindex < MAX_MODELS) + && g_psv.models[ent->v.modelindex]->type != mod_studio) + { + entityState.aiment = 0; + } + } } // Prevent spam "Non-sprite set to glow!" in console on client-side From 1d6c6826db935f3d18928eeb76f688109a52c7ac Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 14 Jan 2024 00:24:33 +0700 Subject: [PATCH 18/30] fix build --- rehlds/engine/cmd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rehlds/engine/cmd.cpp b/rehlds/engine/cmd.cpp index ae541553a..a49df84ae 100644 --- a/rehlds/engine/cmd.cpp +++ b/rehlds/engine/cmd.cpp @@ -235,7 +235,7 @@ void Cbuf_Execute(void) } // execute the command line - if (len > 0) + if (line[0]) Cmd_ExecuteString(line, src_command); if (cmd_wait) From 62407e0dd63542f0da8438ebb22b33d3bcab3b9d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 00:40:16 +0700 Subject: [PATCH 19/30] Implement commands rcon_adduser,rcon_deluser,rcon_users to allow use RCON only by known user IPs (Resolves #796) SV_Rcon: Minor refactoring --- README.md | 3 + rehlds/engine/server.h | 3 + rehlds/engine/sv_main.cpp | 218 +++++++++++++++++++++++++++++++++++--- 3 files changed, 209 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2f41d254a..1c0861712 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
  • rescount // Prints the total count of precached resources in the 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. +
  • rcon_adduser // Add a new IP address or CIDR range to RCON user list (This command adds a new IP address to the RCON user list. The specified IP or CIDR range is granted privileged access to server console. Without any Rcon users, access is allowed to anyone with a valid password)
  • +
  • rcon_deluser {removeAll} // Remove an IP address or CIDR range from RCON user list
  • +
  • rcon_users // List all IP addresses and CIDR ranges in RCON user list
## Build instructions diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h index 5fda2c1ba..e22b889ea 100644 --- a/rehlds/engine/server.h +++ b/rehlds/engine/server.h @@ -587,6 +587,9 @@ void SV_ClearEntities(void); int RegUserMsg(const char *pszName, int iSize); qboolean StringToFilter(const char *s, ipfilter_t *f); USERID_t *SV_StringToUserID(const char *str); +bool CanBeWrittenWithoutCIDR(const ipfilter_t &f); +void FilterToString(const ipfilter_t &f, char *s); +bool IsFilterIncludesAnotherFilter(const ipfilter_t &f, const ipfilter_t &f2); void SV_BanId_f(void); void Host_Kick_f(void); void SV_RemoveId_f(void); diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 6d0387d0b..4b0feafbc 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -3274,6 +3274,159 @@ void SV_ResetRcon_f(void) Q_memset(g_rgRconFailures, 0, sizeof(g_rgRconFailures)); } +const int MAX_RCON_USERS = 128; +ipfilter_t rconusers[MAX_RCON_USERS]; +int numrconusers = 0; + +qboolean SV_CheckRconAllowed(const netadr_t *adr) +{ + if (numrconusers <= 0) + return TRUE; // Rcon user list empty so assume allowed it for all + + for (int i = numrconusers - 1; i >= 0; i--) + { + ipfilter_t *curFilter = &rconusers[i]; + if (curFilter->compare.u32 == 0xFFFFFFFF || (*(uint32*)adr->ip & curFilter->mask) == curFilter->compare.u32) + return TRUE; + } + + return FALSE; +} + +void SV_RconAddUser_f(void) +{ + if (Cmd_Argc() != 2) + { + Con_Printf("Usage: rcon_adduser \n" + "ipaddress A.B.C.D/24 is equivalent to A.B.C.0 and A.B.C\n"); + return; + } + + ipfilter_t tempFilter; + if (!StringToFilter(Cmd_Argv(1), &tempFilter)) + { + Con_Printf("Invalid IP address!\nUsage: rcon_adduser \n"); + return; + } + + int i = 0; + for (; i < numrconusers; i++) + { + if (rconusers[i].mask == tempFilter.mask && rconusers[i].compare.u32 == tempFilter.compare.u32) + { + rconusers[i].cidr = tempFilter.cidr; + return; + } + } + + if (numrconusers >= MAX_RCON_USERS) + { + Con_Printf("IP rcon users is full\n"); + return; + } + + numrconusers++; + rconusers[i].compare = tempFilter.compare; + rconusers[i].mask = tempFilter.mask; + rconusers[i].cidr = tempFilter.cidr; +} + +void SV_RconDelUser_f(void) +{ + int argCount = Cmd_Argc(); + if (argCount != 2 && argCount != 3) + { + Con_Printf("Usage: rcon_deluser {removeAll}\n" + "removeip {removeAll}\n" + "Use removeAll to delete all Rcon ip users which ipaddress or ipaddress/CIDR includes\n"); + + return; + } + + ipfilter_t f; + + if (!StringToFilter(Cmd_Argv(1), &f)) + { + Con_Printf("Invalid IP address\n" + "Usage: rcon_deluser {removeAll}\n" + " rcon_deluser {removeAll}\n" + "Use removeAll to delete all Rcon ip users which ipaddress or ipaddress/CIDR includes\n"); + return; + } + + bool found = false; + for (int i = 0; i < numrconusers; i++) + { + if ((argCount == 2 && rconusers[i].mask == f.mask && rconusers[i].compare.u32 == f.compare.u32) || + (argCount == 3 && IsFilterIncludesAnotherFilter(f, rconusers[i]))) + { + if (i + 1 < numrconusers) + Q_memmove(&rconusers[i], &rconusers[i + 1], (numrconusers - (i + 1)) * sizeof(ipfilter_t)); + numrconusers--; + rconusers[numrconusers].banTime = 0.0f; + rconusers[numrconusers].banEndTime = 0.0f; + rconusers[numrconusers].compare.u32 = 0; + rconusers[numrconusers].mask = 0; + found = true; + --i; + + if (argCount == 2) + break; + } + } + + if (found) + Con_Printf("Rcon user IP removed.\n"); + + else + { + Con_Printf("rcon_deluser: couldn't find %s.\n", Cmd_Argv(1)); + } +} + +void SV_RconUsers_f(void) +{ + if (numrconusers <= 0) + { + Con_Printf("Rcon user IP list: empty\n"); + return; + } + + bool isNew = Cmd_Argc() == 2; + bool searchByFilter = isNew && isdigit(Cmd_Argv(1)[0]); + ipfilter_t filter; + + if (searchByFilter) + { + if (!StringToFilter(Cmd_Argv(1), &filter)) + return; + + Con_Printf("Rcon user IP list for %s:\n", Cmd_Argv(1)); + } + else + { + Con_Printf("Rcon user IP list:\n"); + } + + for (int i = 0; i < numrconusers; i++) + { + uint8 *b = rconusers[i].compare.octets; + if (isNew) + { + if (!searchByFilter || IsFilterIncludesAnotherFilter(filter, rconusers[i])) + { + char strFilter[32]; + FilterToString(rconusers[i], strFilter); + Con_Printf("%-18s\n", strFilter); + } + } + else if (CanBeWrittenWithoutCIDR(rconusers[i])) + { + Con_Printf("%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]); + } + } +} + void SV_AddFailedRcon(netadr_t *adr) { int i; @@ -3402,10 +3555,21 @@ qboolean SV_CheckRconFailure(netadr_t *adr) return FALSE; } +#define RCON_RESULT_SUCCESS 0 // allow the rcon +#define RCON_RESULT_BADPASSWORD 1 // reject it, bad password +#define RCON_RESULT_BADCHALLENGE 2 // bad challenge +#define RCON_RESULT_BANNING 3 // decline it, banning for rcon hacking attempts +#define RCON_RESULT_NOSETPASSWORD 4 // rcon password is not set +#define RCON_RESULT_NOPRIVILEGE 5 // user attempt with valid password but is not privileged + int SV_Rcon_Validate(void) { - if (Cmd_Argc() < 3 || Q_strlen(rcon_password.string) == 0) - return 1; + if (Cmd_Argc() < 3) + return RCON_RESULT_BADPASSWORD; + + // Must have a password set to allow any rconning + if (Q_strlen(rcon_password.string) == 0) + return RCON_RESULT_NOSETPASSWORD; if (sv_rcon_banpenalty.value < 0.0f) Cvar_SetValue("sv_rcon_banpenalty", 0.0); @@ -3414,18 +3578,27 @@ int SV_Rcon_Validate(void) { Con_Printf("Banning %s for rcon hacking attempts\n", NET_AdrToString(net_from)); Cbuf_AddText(va("addip %i %s\n", (int)sv_rcon_banpenalty.value, NET_BaseAdrToString(net_from))); - return 3; + return RCON_RESULT_BANNING; } if (!SV_CheckChallenge(&net_from, Q_atoi(Cmd_Argv(1)))) - return 2; + return RCON_RESULT_BADCHALLENGE; if (Q_strcmp(Cmd_Argv(2), rcon_password.string)) { SV_AddFailedRcon(&net_from); - return 1; + return RCON_RESULT_BADPASSWORD; } - return 0; + + if (!SV_CheckRconAllowed(&net_from)) + { + Con_Printf("Banning %s for rcon attempts without privileged\n", NET_AdrToString(net_from)); + Cbuf_AddText(va("addip %i %s\n", (int)sv_rcon_banpenalty.value, NET_BaseAdrToString(net_from))); + return RCON_RESULT_NOPRIVILEGE; + } + + // Otherwise it's ok + return RCON_RESULT_SUCCESS; } void SV_Rcon(netadr_t *net_from_) @@ -3445,7 +3618,7 @@ void SV_Rcon(netadr_t *net_from_) if (sv_rcon_condebug.value > 0.0f) #endif { - if (invalid) + if (invalid != RCON_RESULT_SUCCESS) { Con_Printf("Bad Rcon from %s:\n%s\n", NET_AdrToString(*net_from_), remaining); Log_Printf("Bad Rcon: \"%s\" from \"%s\"\n", remaining, NET_AdrToString(*net_from_)); @@ -3459,18 +3632,28 @@ void SV_Rcon(netadr_t *net_from_) SV_BeginRedirect(RD_PACKET, net_from_); - if (invalid) + switch (invalid) { - if (invalid == 2) - Con_Printf("Bad rcon_password.\n"); - else if (Q_strlen(rcon_password.string) == 0) - Con_Printf("Bad rcon_password.\nNo password set for this server.\n"); - else - Con_Printf("Bad rcon_password.\n"); - + case RCON_RESULT_SUCCESS: + break; + case RCON_RESULT_BANNING: + case RCON_RESULT_BADPASSWORD: + Con_Printf("Bad rcon_password.\n"); + // fall through + case RCON_RESULT_NOPRIVILEGE: + Con_Printf("Bad rcon_password.\nNo privilege.\n"); + // fall through + case RCON_RESULT_NOSETPASSWORD: + Con_Printf("Bad rcon_password.\nNo password set for this server.\n"); + // fall through + case RCON_RESULT_BADCHALLENGE: + Con_Printf("Bad rcon_password.\nBad challenge.\n"); + // fall through + default: SV_EndRedirect(); return; } + char *data = COM_Parse(COM_Parse(COM_Parse(remaining))); if (!data) { @@ -7995,6 +8178,11 @@ void SV_Init(void) Cmd_AddCommand("listid", SV_ListId_f); Cmd_AddCommand("writeid", SV_WriteId_f); Cmd_AddCommand("resetrcon", SV_ResetRcon_f); +#ifdef REHLDS_FIXES + Cmd_AddCommand("rcon_adduser", SV_RconAddUser_f); + Cmd_AddCommand("rcon_deluser", SV_RconDelUser_f); + Cmd_AddCommand("rcon_users", SV_RconUsers_f); +#endif Cmd_AddCommand("logaddress", SV_SetLogAddress_f); Cmd_AddCommand("logaddress_add", SV_AddLogAddress_f); Cmd_AddCommand("logaddress_del", SV_DelLogAddress_f); From 355172d6db9f8a1514ef82a3b2d704cc6df07b15 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 00:46:45 +0700 Subject: [PATCH 20/30] Fix unit-test --- rehlds/engine/filter.h | 2 -- rehlds/engine/sv_main.cpp | 39 --------------------------------------- 2 files changed, 41 deletions(-) diff --git a/rehlds/engine/filter.h b/rehlds/engine/filter.h index b2a080be6..84c7afe37 100644 --- a/rehlds/engine/filter.h +++ b/rehlds/engine/filter.h @@ -42,9 +42,7 @@ typedef struct ipfilter_s } compare; float banEndTime; float banTime; -#ifdef REHLDS_FIXES int cidr; -#endif // REHLDS_FIXES } ipfilter_t; typedef struct userfilter_s diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 4b0feafbc..d3e1ff7bd 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -6553,7 +6553,6 @@ int EXT_FUNC RegUserMsg(const char *pszName, int iSize) return pNewMsg->iMsg; } -#ifdef REHLDS_FIXES uint32_t CIDRToMask(int cidr) { return htonl(0xFFFFFFFFull << (32 - cidr)); @@ -6703,44 +6702,6 @@ qboolean StringToFilter(const char *s, ipfilter_t *f) return true; } -#else // REHLDS_FIXES -qboolean StringToFilter(const char *s, ipfilter_t *f) -{ - char num[128]; - unsigned char b[4] = { 0, 0, 0, 0 }; - unsigned char m[4] = { 0, 0, 0, 0 }; - - const char* cc = s; - int i = 0; - while (1) - { - if (*cc < '0' || *cc > '9') - break; - - int j = 0; - while (*cc >= '0' && *cc <= '9') - num[j++] = *(cc++); - - num[j] = 0; - b[i] = Q_atoi(num); - if (b[i]) - m[i] = -1; - - if (*cc) - { - ++cc; - ++i; - if (i < 4) - continue; - } - f->mask = *(uint32 *)m; - f->compare.u32 = *(uint32 *)b; - return TRUE; - } - Con_Printf("Bad filter address: %s\n", cc); - return FALSE; -} -#endif // REHLDS_FIXES USERID_t *SV_StringToUserID(const char *str) { From f29d6c5769b256ea57829435ac018cf5494826f6 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 00:56:32 +0700 Subject: [PATCH 21/30] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c0861712..0bcb7b855 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
  • rescount // Prints the total count of precached resources in the 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. -
  • rcon_adduser // Add a new IP address or CIDR range to RCON user list (This command adds a new IP address to the RCON user list. The specified IP or CIDR range is granted privileged access to server console. Without any Rcon users, access is allowed to anyone with a valid password)
  • -
  • rcon_deluser {removeAll} // Remove an IP address or CIDR range from RCON user list
  • +
  • rcon_adduser <ipaddress/CIDR> // Add a new IP address or CIDR range to RCON user list (This command adds a new IP address to the RCON user list. The specified IP or CIDR range is granted privileged access to server console. Without any Rcon users, access is allowed to anyone with a valid password)
  • +
  • rcon_deluser <ipaddress> {removeAll} // Remove an IP address or CIDR range from RCON user list
  • rcon_users // List all IP addresses and CIDR ranges in RCON user list
From 76cbd2c14025b3e54bc14b8fcfe17ce5fa273195 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 21:38:33 +0700 Subject: [PATCH 22/30] Implemented optional CVar sv_tags for sets a string defining the "gametags" for this server to allows users/scripts to filter in the matchmaking/server-browser interfaces based on the value --- README.md | 1 + rehlds/engine/server.h | 1 + rehlds/engine/sv_main.cpp | 3 +++ rehlds/engine/sv_steam3.cpp | 21 +++++++++++++++++++++ rehlds/engine/sv_steam3.h | 7 +++++++ rehlds/rehlds/structSizeCheck.cpp | 2 ++ 6 files changed, 35 insertions(+) diff --git a/README.md b/README.md index 0bcb7b855..f9756ed0d 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
  • sv_rehlds_local_gametime <1|0> // A feature of local gametime which decrease "lags" if you run same map for a long time. Default: 0
  • sv_use_entity_file // Use custom entity file for a map. Path to an entity file will be "maps/[map name].ent". 0 - use original entities. 1 - use .ent files from maps directory. 2 - use .ent files from maps directory and create new .ent file if not exist.
  • sv_usercmd_custom_random_seed // When enabled server will populate an additional random seed independent of the client. Default: 0 +
  • sv_tags // Sets a string defining the "gametags" for this server, this is optional, but if it is set it allows users/scripts to filter in the matchmaking/server-browser interfaces based on the value. Default: "" diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h index e22b889ea..c59332e16 100644 --- a/rehlds/engine/server.h +++ b/rehlds/engine/server.h @@ -366,6 +366,7 @@ extern cvar_t sv_visiblemaxplayers; extern cvar_t sv_downloadurl; extern cvar_t sv_allow_dlfile; extern cvar_t sv_version; +extern cvar_t sv_tags; #ifdef REHLDS_FIXES extern cvar_t sv_echo_unknown_cmd; extern cvar_t sv_auto_precache_sounds_in_models; diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index d3e1ff7bd..59d35884c 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -193,6 +193,8 @@ cvar_t sv_version = { "sv_version", "", FCVAR_SERVER, 0.0f, NULL }; cvar_t sv_version = {"sv_version", "", 0, 0.0f, NULL}; #endif +cvar_t sv_tags = { "sv_tags", "", 0, 0.0f, NULL }; + cvar_t sv_rcon_minfailures = { "sv_rcon_minfailures", "5", 0, 0.0f, NULL }; cvar_t sv_rcon_maxfailures = { "sv_rcon_maxfailures", "10", 0, 0.0f, NULL }; cvar_t sv_rcon_minfailuretime = { "sv_rcon_minfailuretime", "30", 0, 0.0f, NULL }; @@ -8258,6 +8260,7 @@ void SV_Init(void) Cvar_RegisterVariable(&sv_version); Cvar_RegisterVariable(&sv_allow_dlfile); #ifdef REHLDS_FIXES + Cvar_RegisterVariable(&sv_tags); Cvar_RegisterVariable(&sv_force_ent_intersection); Cvar_RegisterVariable(&sv_echo_unknown_cmd); Cvar_RegisterVariable(&sv_auto_precache_sounds_in_models); diff --git a/rehlds/engine/sv_steam3.cpp b/rehlds/engine/sv_steam3.cpp index ad288fa57..8cd18a66e 100644 --- a/rehlds/engine/sv_steam3.cpp +++ b/rehlds/engine/sv_steam3.cpp @@ -232,6 +232,10 @@ CSteam3Server::CSteam3Server() : m_CallbackLogonFailure(this, &CSteam3Server::OnLogonFailure), m_SteamIDGS(1, 0, k_EUniverseInvalid, k_EAccountTypeInvalid) { +#ifdef REHLDS_FIXES + m_GameTagsData[0] = '\0'; +#endif + m_bHasActivePlayers = false; m_bWantToBeSecure = false; m_bLanOnly = false; @@ -499,6 +503,21 @@ void CSteam3Server::RunFrame() } } +void CSteam3Server::UpdateGameTags() +{ +#ifdef REHLDS_FIXES + if (!sv_tags.string[0]) + return; + + if (m_GameTagsData[0] && !Q_stricmp(m_GameTagsData, sv_tags.string)) + return; + + Q_strlcpy(m_GameTagsData, sv_tags.string); + Q_strlwr(m_GameTagsData); + CRehldsPlatformHolder::get()->SteamGameServer()->SetGameTags(m_GameTagsData); +#endif +} + void CSteam3Server::SendUpdatedServerDetails() { int botCount = 0; @@ -521,6 +540,8 @@ void CSteam3Server::SendUpdatedServerDetails() CRehldsPlatformHolder::get()->SteamGameServer()->SetBotPlayerCount(botCount); CRehldsPlatformHolder::get()->SteamGameServer()->SetServerName(Cvar_VariableString("hostname")); CRehldsPlatformHolder::get()->SteamGameServer()->SetMapName(g_psv.name); + + UpdateGameTags(); } void CSteam3Client::Shutdown() diff --git a/rehlds/engine/sv_steam3.h b/rehlds/engine/sv_steam3.h index 1aa2e51aa..3238dc87b 100644 --- a/rehlds/engine/sv_steam3.h +++ b/rehlds/engine/sv_steam3.h @@ -54,6 +54,8 @@ class CSteam3 bool InitModule(); }; +#define MAX_STEAM_TAGS_LENGTH 128 // Steam doesn't send tags string more than 128 bytes + class CSteam3Server: public CSteam3 { public: @@ -71,6 +73,10 @@ class CSteam3Server: public CSteam3 bool m_bLanOnly; CSteamID m_SteamIDGS; +#ifdef REHLDS_FIXES + char m_GameTagsData[MAX_STEAM_TAGS_LENGTH]; +#endif + public: NOBODY void SetServerType(); @@ -96,6 +102,7 @@ class CSteam3Server: public CSteam3 void NotifyOfLevelChange(bool bForce); void RunFrame(); void SendUpdatedServerDetails(); + void UpdateGameTags(); }; class CSteam3Client: public CSteam3 diff --git a/rehlds/rehlds/structSizeCheck.cpp b/rehlds/rehlds/structSizeCheck.cpp index 18cb3db2a..259787c3e 100644 --- a/rehlds/rehlds/structSizeCheck.cpp +++ b/rehlds/rehlds/structSizeCheck.cpp @@ -15,5 +15,7 @@ void check_size() { void checkSizesStatic() { CHECK_TYPE_SIZE(client_t, 0x5018, 0x4EF4); CHECK_TYPE_SIZE(userfilter_t, 0x20, 0x18); +#ifndef REHLDS_FIXES CHECK_TYPE_SIZE(CSteam3Server, 0x90, 0xA8); +#endif } From 90cb97ddae6cd7c66b08987d4d8049cb9fbcc76d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 21:50:33 +0700 Subject: [PATCH 23/30] Update README.md --- README.md | 24 ++++++++++++------------ rehlds/engine/sv_steam3.cpp | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f9756ed0d..7ee72b71d 100644 --- a/README.md +++ b/README.md @@ -31,20 +31,20 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
    Click to expand
      -
    • listipcfgfile // File for permanent ip bans. Default: listip.cfg -
    • syserror_logfile // File for the system error log. Default: sys_error.log -
    • sv_auto_precache_sounds_in_models <1|0> // Automatically precache sounds attached to models. Deault: 0 -
    • sv_delayed_spray_upload <1|0> // Upload custom sprays after entering the game instead of when connecting. It increases upload speed. Default: 0 -
    • sv_echo_unknown_cmd <1|0> // Echo in the console when trying execute an unknown command. Default: 0 -
    • sv_rcon_condebug <1|0> // Print rcon debug in the console. Default: 1 -
    • sv_force_ent_intersection <1|0> // In a 3-rd party plugins used to force colliding of SOLID_SLIDEBOX entities. Default: 0 -
    • sv_rehlds_force_dlmax <1|0> // Force a client's cl_dlmax cvar to 1024. It avoids an excessive packets fragmentation. Default: 0 -
    • sv_rehlds_hull_centering <1|0> // Use center of hull instead of corner. Default: 0 +
    • listipcfgfile <filename> // File for permanent ip bans. Default: listip.cfg +
    • syserror_logfile <filename> // File for the system error log. Default: sys_error.log +
    • sv_auto_precache_sounds_in_models <1|0> // Automatically precache sounds attached to models. Deault: 0 +
    • sv_delayed_spray_upload <1|0> // Upload custom sprays after entering the game instead of when connecting. It increases upload speed. Default: 0 +
    • sv_echo_unknown_cmd <1|0> // Echo in the console when trying execute an unknown command. Default: 0 +
    • sv_rcon_condebug <1|0> // Print rcon debug in the console. Default: 1 +
    • sv_force_ent_intersection <1|0> // In a 3-rd party plugins used to force colliding of SOLID_SLIDEBOX entities. Default: 0 +
    • sv_rehlds_force_dlmax <1|0> // Force a client's cl_dlmax cvar to 1024. It avoids an excessive packets fragmentation. Default: 0 +
    • sv_rehlds_hull_centering <1|0> // Use center of hull instead of corner. Default: 0
    • sv_rehlds_movecmdrate_max_avg // Max average level of 'move' cmds for ban. Default: 400
    • sv_rehlds_movecmdrate_avg_punish // Time in minutes for which the player will be banned (0 - Permanent, use a negative number for a kick). Default: 5
    • sv_rehlds_movecmdrate_max_burst // Max burst level of 'move' cmds for ban. Default: 2500
    • sv_rehlds_movecmdrate_burst_punish // Time in minutes for which the player will be banned (0 - Permanent, use a negative number for a kick). Default: 5 -
    • sv_rehlds_send_mapcycle <1|0> // Send mapcycle.txt in serverinfo message (HLDS behavior, but it is unused on the client). Default: 0 +
    • sv_rehlds_send_mapcycle <1|0> // Send mapcycle.txt in serverinfo message (HLDS behavior, but it is unused on the client). Default: 0
    • sv_rehlds_stringcmdrate_max_avg // Max average level of 'string' cmds for ban. Default: 80
    • sv_rehlds_stringcmdrate_avg_punish // Time in minutes for which the player will be banned (0 - Permanent, use a negative number for a kick). Default: 5
    • sv_rehlds_stringcmdrate_max_burst // Max burst level of 'string' cmds for ban. Default: 400 @@ -52,10 +52,10 @@ This means that plugins that do binary code analysis (Orpheu for example) probab
    • sv_rehlds_userinfo_transmitted_fields // Userinfo fields only with these keys will be transmitted to clients via network. If not set then all fields will be transmitted (except prefixed with underscore). Each key must be prefixed by backslash, for example "\name\model\*sid\*hltv\bottomcolor\topcolor". See [wiki](https://github.com/dreamstalker/rehlds/wiki/Userinfo-keys) to collect sufficient set of keys for your server. Default: ""
    • 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
    • sv_rehlds_maxclients_from_single_ip // Limit number of connections at the same time from single IP address, not confuse to already connected players. Default: 5 -
    • sv_rehlds_local_gametime <1|0> // A feature of local gametime which decrease "lags" if you run same map for a long time. Default: 0 +
    • sv_rehlds_local_gametime <1|0> // A feature of local gametime which decrease "lags" if you run same map for a long time. Default: 0
    • sv_use_entity_file // Use custom entity file for a map. Path to an entity file will be "maps/[map name].ent". 0 - use original entities. 1 - use .ent files from maps directory. 2 - use .ent files from maps directory and create new .ent file if not exist.
    • sv_usercmd_custom_random_seed // When enabled server will populate an additional random seed independent of the client. Default: 0 -
    • sv_tags // Sets a string defining the "gametags" for this server, this is optional, but if it is set it allows users/scripts to filter in the matchmaking/server-browser interfaces based on the value. Default: "" +
    • sv_tags <comma-delimited string list of tags> // Sets a string defining the "gametags" for this server, this is optional, but if it is set it allows users/scripts to filter in the matchmaking/server-browser interfaces based on the value. Default: ""
    diff --git a/rehlds/engine/sv_steam3.cpp b/rehlds/engine/sv_steam3.cpp index 8cd18a66e..c35b29343 100644 --- a/rehlds/engine/sv_steam3.cpp +++ b/rehlds/engine/sv_steam3.cpp @@ -506,7 +506,7 @@ void CSteam3Server::RunFrame() void CSteam3Server::UpdateGameTags() { #ifdef REHLDS_FIXES - if (!sv_tags.string[0]) + if (!m_GameTagsData[0] && !sv_tags.string[0]) return; if (m_GameTagsData[0] && !Q_stricmp(m_GameTagsData, sv_tags.string)) From 41c5186b2c8c00f49101ed2c5d81a47cf79449c4 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 22 Jan 2024 05:48:34 +0700 Subject: [PATCH 24/30] RCON: Fixes redirect print and minor refactoring --- rehlds/engine/sv_main.cpp | 76 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 59d35884c..14321da54 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -3584,8 +3584,9 @@ int SV_Rcon_Validate(void) } if (!SV_CheckChallenge(&net_from, Q_atoi(Cmd_Argv(1)))) - return RCON_RESULT_BADCHALLENGE; + return RCON_RESULT_BADCHALLENGE; // The client is spoofing... + // If the pw does not match, then disallow command if (Q_strcmp(Cmd_Argv(2), rcon_password.string)) { SV_AddFailedRcon(&net_from); @@ -3603,18 +3604,24 @@ int SV_Rcon_Validate(void) return RCON_RESULT_SUCCESS; } +// A client issued an rcom command +// Shift down the remaining args and redirect all Con_Printf void SV_Rcon(netadr_t *net_from_) { - char remaining[512]; - char rcon_buff[1024]; + int invalid; + char remaining[1024]; + char rcon_buff[512]; + int len; - int invalid = SV_Rcon_Validate(); - int len = net_message.cursize - Q_strlen("rcon"); - if (len <= 0 || len >= sizeof(remaining)) + // Verify this user has access rights + invalid = SV_Rcon_Validate(); + + len = net_message.cursize - Q_strlen("rcon"); + if (len <= 0 || len >= sizeof(rcon_buff)) return; - Q_memcpy(remaining, &net_message.data[Q_strlen("rcon")], len); - remaining[len] = 0; + Q_memcpy(rcon_buff, &net_message.data[Q_strlen("rcon")], len); + rcon_buff[len] = 0; #ifdef REHLDS_FIXES if (sv_rcon_condebug.value > 0.0f) @@ -3622,13 +3629,13 @@ void SV_Rcon(netadr_t *net_from_) { if (invalid != RCON_RESULT_SUCCESS) { - Con_Printf("Bad Rcon from %s:\n%s\n", NET_AdrToString(*net_from_), remaining); - Log_Printf("Bad Rcon: \"%s\" from \"%s\"\n", remaining, NET_AdrToString(*net_from_)); + Con_Printf("Bad Rcon from %s:\n%s\n", NET_AdrToString(*net_from_), rcon_buff); + Log_Printf("Bad Rcon: \"%s\" from \"%s\"\n", rcon_buff, NET_AdrToString(*net_from_)); } else { - Con_Printf("Rcon from %s:\n%s\n", NET_AdrToString(*net_from_), remaining); - Log_Printf("Rcon: \"%s\" from \"%s\"\n", remaining, NET_AdrToString(*net_from_)); + Con_Printf("Rcon from %s:\n%s\n", NET_AdrToString(*net_from_), rcon_buff); + Log_Printf("Rcon: \"%s\" from \"%s\"\n", rcon_buff, NET_AdrToString(*net_from_)); } } @@ -3637,40 +3644,41 @@ void SV_Rcon(netadr_t *net_from_) switch (invalid) { case RCON_RESULT_SUCCESS: + { + char *data; + data = COM_Parse(rcon_buff); + data = COM_Parse(data); + data = COM_Parse(data); + + if (data) + { + Q_strncpy(remaining, data, sizeof(remaining) - 1); + remaining[sizeof(remaining) - 1] = 0; + + Cmd_ExecuteString(remaining, src_command); + } + else + { + Con_Printf("Empty rcon\n"); + } + break; + } case RCON_RESULT_BANNING: case RCON_RESULT_BADPASSWORD: Con_Printf("Bad rcon_password.\n"); - // fall through + break; case RCON_RESULT_NOPRIVILEGE: Con_Printf("Bad rcon_password.\nNo privilege.\n"); - // fall through + break; case RCON_RESULT_NOSETPASSWORD: Con_Printf("Bad rcon_password.\nNo password set for this server.\n"); - // fall through + break; case RCON_RESULT_BADCHALLENGE: Con_Printf("Bad rcon_password.\nBad challenge.\n"); - // fall through - default: - SV_EndRedirect(); - return; - } - - char *data = COM_Parse(COM_Parse(COM_Parse(remaining))); - if (!data) - { - Con_Printf("Empty rcon\n"); - -#ifdef REHLDS_FIXES - //missing SV_EndRedirect() - SV_EndRedirect(); -#endif // REHLDS_FIXES - return; + break; } - Q_strncpy(rcon_buff, data, sizeof(rcon_buff) - 1); - rcon_buff[sizeof(rcon_buff) - 1] = 0; - Cmd_ExecuteString(rcon_buff, src_command); SV_EndRedirect(); } From 9b0dbe8dd2ced5f0ead251116f5b9ebd61f6d6a4 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 25 Jan 2024 18:36:03 +0700 Subject: [PATCH 25/30] Host_Status_f: Fixed incorrect player index to output --- rehlds/engine/host_cmd.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rehlds/engine/host_cmd.cpp b/rehlds/engine/host_cmd.cpp index 993e56abd..79996ab33 100644 --- a/rehlds/engine/host_cmd.cpp +++ b/rehlds/engine/host_cmd.cpp @@ -610,7 +610,6 @@ void Host_Status_f(void) Host_Status_Printf(conprint, log, "players : %i active (%i max)\n\n", nClients, g_psvs.maxclients); Host_Status_Printf(conprint, log, "# name userid uniqueid frag time ping loss adr\n"); - int count = 1; client = g_psvs.clients; for (j = 0; j < g_psvs.maxclients; j++, client++) { @@ -634,7 +633,7 @@ void Host_Status_f(void) val = SV_GetClientIDString(client); else val = "BOT"; - Host_Status_Printf(conprint, log, "#%2i %8s %i %s", count++, va("\"%s\"", client->name), client->userid, val); + Host_Status_Printf(conprint, log, "#%2i %8s %i %s", j + 1, va("\"%s\"", client->name), client->userid, val); if (client->proxy) { const char *userInfo = Info_ValueForKey(client->userinfo, "hspecs"); @@ -724,7 +723,6 @@ void Host_Status_Formatted_f(void) Host_Status_Printf(conprint, log, "players : %i active (%i max)\n\n", nClients, g_psvs.maxclients); Host_Status_Printf(conprint, log, "%-2.2s\t%-9.9s\t%-7.7s\t%-20.20s\t%-4.4s\t%-8.8s\t%-4.4s\t%-4.4s\t%-21.21s\n","# ","name","userid ","uniqueid ","frag","time ","ping","loss","adr"); - int count = 1; char *szRemoteAddr; client = g_psvs.clients; for (j = 0; j < g_psvs.maxclients; j++, client++) @@ -755,7 +753,7 @@ void Host_Status_Formatted_f(void) #endif // REHLDS_FIXES szIDString = SV_GetClientIDString(client); Host_Status_Printf(conprint, log, "%-2.2s\t%-9.9s\t%-7.7s\t%-20.20s\t%-4.4s\t%-8.8s\t%-4.4s\t%-4.4s\t%-21.21s\n", - va("%-2i", count++),va("\"%s\"", client->name),va("%-7i", client->userid),szIDString, + va("%-2i", j + 1),va("\"%s\"", client->name),va("%-7i", client->userid),szIDString, va("%-4i", (int)client->edict->v.frags),sz,va("%-4i", SV_CalcPing(client)),va("%-4i", (int)client->packet_loss),szRemoteAddr); } Host_Status_Printf(conprint, log, "%i users\n", nClients); From 63fde229c98f7e1bb8f7ea9b8358027414ed26ac Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 25 Jan 2024 19:04:48 +0700 Subject: [PATCH 26/30] MSG_WriteBitAngle: Cap the precision check from 32 to 22 to avoid overflow issues when representing angles with more than 22 bits because the multiply by 'shift' may result in overflow --- rehlds/engine/common.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rehlds/engine/common.cpp b/rehlds/engine/common.cpp index e70d03ca1..3ce8aa47a 100644 --- a/rehlds/engine/common.cpp +++ b/rehlds/engine/common.cpp @@ -586,9 +586,9 @@ void MSG_WriteBitData(void *src, int length) void MSG_WriteBitAngle(float fAngle, int numbits) { - if (numbits >= 32) + if (numbits > 22) { - Sys_Error("%s: Can't write bit angle with 32 bits precision\n", __func__); + Sys_Error("%s: Can't write bit angle with more than 22 bits precision\n", __func__); } uint32 shift = (1 << numbits); From a7b60451f33e076ba04e4ae38312c2f691540a2d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 31 Jan 2024 15:41:00 +0700 Subject: [PATCH 27/30] TEX_InitFromWad: Fix reversing mistake (Don't add file handle before check) --- rehlds/engine/textures.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rehlds/engine/textures.cpp b/rehlds/engine/textures.cpp index 17b65b3ad..4c72dfd55 100644 --- a/rehlds/engine/textures.cpp +++ b/rehlds/engine/textures.cpp @@ -109,10 +109,10 @@ qboolean TEX_InitFromWad(char *path) #endif // REHLDS_FIXES texfile = FS_Open(wadPath, "rb"); - texfiles[nTexFiles++] = texfile; if (!texfile) Sys_Error("%s: couldn't open %s\n", __func__, wadPath); + texfiles[nTexFiles++] = texfile; Con_DPrintf("Using WAD File: %s\n", wadPath); SafeRead(texfile, &header, 12); if (Q_strncmp(header.identification, "WAD2", 4) && Q_strncmp(header.identification, "WAD3", 4)) @@ -147,8 +147,9 @@ void TEX_CleanupWadInfo(void) for (int i = 0; i < nTexFiles; i++) { - FS_Close(texfiles[i]); - texfiles[i] = 0; + if (texfiles[i]) + FS_Close(texfiles[i]); + texfiles[i] = NULL; } nTexLumps = 0; From 498d7e0d18060f53a65286fcbf91e772f42c5611 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 14 Feb 2024 19:23:16 +0700 Subject: [PATCH 28/30] Fix crash when the entity with aiment doesn't have a model --- rehlds/engine/sv_main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 14321da54..411ca39fb 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -4904,8 +4904,9 @@ void SV_WriteEntitiesToClient(client_t *client, sizebuf_t *msg) if (entityState.aiment < g_psv.num_edicts) { edict_t *ent = &g_psv.edicts[entityState.aiment]; - if ((ent->v.modelindex >= 0 && ent->v.modelindex < MAX_MODELS) - && g_psv.models[ent->v.modelindex]->type != mod_studio) + if (ent->v.modelindex >= 0 && ent->v.modelindex < MAX_MODELS + && (!g_psv.models[ent->v.modelindex] + || g_psv.models[ent->v.modelindex]->type != mod_studio)) { entityState.aiment = 0; } @@ -4915,6 +4916,7 @@ void SV_WriteEntitiesToClient(client_t *client, sizebuf_t *msg) // Prevent spam "Non-sprite set to glow!" in console on client-side if (entityState.rendermode == kRenderGlow && (entityState.modelindex >= 0 && entityState.modelindex < MAX_MODELS) + && g_psv.models[entityState.modelindex] && g_psv.models[entityState.modelindex]->type != mod_sprite) { entityState.rendermode = kRenderNormal; From 58391b6ee5e5faefcf84349443a2a36066ed8246 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Tue, 20 Feb 2024 03:18:36 +0700 Subject: [PATCH 29/30] SV_WriteEntitiesToClient: Reset movetype if the aiment index is invalid --- rehlds/engine/sv_main.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/rehlds/engine/sv_main.cpp b/rehlds/engine/sv_main.cpp index 411ca39fb..870a5fabb 100644 --- a/rehlds/engine/sv_main.cpp +++ b/rehlds/engine/sv_main.cpp @@ -4891,26 +4891,32 @@ void SV_WriteEntitiesToClient(client_t *client, sizebuf_t *msg) auto &entityState = curPack->entities[i]; if (entityState.number > MAX_CLIENTS) { - if (entityState.movetype == MOVETYPE_FOLLOW && entityState.aiment > 0) + if (entityState.movetype == MOVETYPE_FOLLOW) { - if (sv_rehlds_attachedentities_playeranimationspeed_fix.string[0] == '1' && - entityState.aiment <= MAX_CLIENTS) + if (entityState.aiment > 0 && entityState.aiment < g_psv.num_edicts) { - attachedEntCount[entityState.aiment]++; - } + if (sv_rehlds_attachedentities_playeranimationspeed_fix.string[0] == '1' && + entityState.aiment <= MAX_CLIENTS) + { + attachedEntCount[entityState.aiment]++; + } - // Prevent crash "Cache_UnlinkLRU: NULL link" on client-side - // if aiment with sprite model will be to render as a studio model - if (entityState.aiment < g_psv.num_edicts) - { + // Prevent crash "Cache_UnlinkLRU: NULL link" on client-side + // if aiment with sprite model will be to render as a studio model edict_t *ent = &g_psv.edicts[entityState.aiment]; if (ent->v.modelindex >= 0 && ent->v.modelindex < MAX_MODELS && (!g_psv.models[ent->v.modelindex] || g_psv.models[ent->v.modelindex]->type != mod_studio)) { entityState.aiment = 0; + entityState.movetype = MOVETYPE_NONE; } } + else + { + entityState.aiment = 0; + entityState.movetype = MOVETYPE_NONE; + } } // Prevent spam "Non-sprite set to glow!" in console on client-side From 05f4a279f9f30cac4d361edebab31dc4a6c1f65f Mon Sep 17 00:00:00 2001 From: anzz1 Date: Sat, 2 Mar 2024 17:24:04 +0200 Subject: [PATCH 30/30] Add steamcmd instructions to readme (#1021) --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ee72b71d..15439cde3 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,16 @@ You can try playing on one of many servers that are using ReHLDS: [Game Tracker] ## How can use it? -ReHLDS is fully compatible with latest official HLDS downloaded by steamcmd. All you have to do is to download rehlds binaries and replace original swds.dll/engine_i486.so. For windows you can also copy a swds.pdb file with a debug information. -
    Warning! ReHLDS is not compatible with an old 5xxx or below platforms downloaded by hldsupdatetool. +ReHLDS is fully compatible with the official pre-anniversary edition of HLDS (engine version <= 8684) downloaded by steamcmd. All you have to do is to download rehlds binaries and replace original swds.dll/engine_i486.so. For windows you can also copy a swds.pdb file with a debug information. + +Warning! ReHLDS is not compatible with an old 5xxx or below platforms downloaded by hldsupdatetool. + +#### Downloading HLDS via steamcmd + +``` +app_set_config 90 mod cstrike +app_update 90 -beta steam_legacy validate +``` ## Downloads * [Release builds](https://github.com/dreamstalker/rehlds/releases)