From 62407e0dd63542f0da8438ebb22b33d3bcab3b9d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 18 Jan 2024 00:40:16 +0700 Subject: [PATCH 01/12] 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 2f41d25..1c08617 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ This means that plugins that do binary code analysis (Orpheu for example) probab ## Build instructions diff --git a/rehlds/engine/server.h b/rehlds/engine/server.h index 5fda2c1..e22b889 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 6d0387d..4b0feaf 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 02/12] 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 b2a080b..84c7afe 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 4b0feaf..d3e1ff7 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 03/12] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c08617..0bcb7b8 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 04/12] 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 0bcb7b8..f9756ed 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 e22b889..c59332e 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 d3e1ff7..59d3588 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 ad288fa..8cd18a6 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 1aa2e51..3238dc8 100644 --- a/rehlds/engine/sv_steam3.h +++ b/rehlds/engine/sv_steam3.h @@ -54,6 +54,8 @@ protected: 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 @@ protected: 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 @@ public: 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 18cb3db..259787c 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 05/12] 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 f9756ed..7ee72b7 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 8cd18a6..c35b293 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 06/12] 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 59d3588..14321da 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; + break; } - 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; - } - - 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 07/12] 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 993e56a..79996ab 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 08/12] 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 e70d03c..3ce8aa4 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 09/12] 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 17b65b3..4c72dfd 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 10/12] 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 14321da..411ca39 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 11/12] 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 411ca39..870a5fa 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 12/12] 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 7ee72b7..15439cd 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)