--- combined lua fixes -- version: 5 -- changelog: -- Just converted the original to a more readable and cleaner format. -- Some stuff was poorly copy pasted in. -- Optimized it slightly, but doubt it has noticiable affect. -- Also there were to et_Print calls so one of them wasn't working. -- and some similar areas -- VERSION 5 -- Was more thorough with sniper classes, It should prevent the weapon class glitch. -- Fixed bug where panzer/rnade/mg could stay after config switch. -- last updated 04/05/16 --- GLOBALS local MODNAME = "combinedfixes" local VERSION = "0.5" local BADNAMES = { '^ShutdownGame', '^ClientBegin', '^ClientDisconnect', '^ExitLevel', '^Timelimit', '^EndRound', '^etpro IAC', '^Vote', '^etpro privmsg', '^Callvote', '^broadcast', -- "say" is relatively likely to have false positives -- but can potentially be used to exploit things that use etadmin_mod style !commands -- '^say', } local CONFIG = "" local GAMEFORMAT = 0 local GAMESTATE = 0 local MAXCLIENTS = 0 local IC_LAST = 0 local IC_FREQ = 5000 local IC_CLIENT = 0 local DEF_GUIDCHECK_BANTIME = 0 -- cvar add "guidcheck_bantime" local DEF_IP_MAX_CLIENTS = 3 -- cvar add "ip_max_clients" et.CS_VOTE_STRING = 7 et.TEAM_AXIS = 1 et.TEAM_ALLIES = 2 et.TEAM_SPECTATOR = 3 et.WEAPON_GARAND = 25 et.WEAPON_GARAND_NADE = 24 et.WEAPON_K43 = 32 et.WEAPON_K43_NADE = 23 et.WEAPON_MP40 = 3 et.WEAPON_STEN = 10 et.WEAPON_MG42 = 31 et.WEAPON_THOMPSON = 8 et.CLASS_SOLDIER = 0 et.CLASS_MEDIC = 1 et.CLASS_ENGINEER = 2 et.CLASS_FIELDOPS = 3 et.CLASS_COVERTOPS = 4 local clientSprint = { } --- LOCAL HELPER FUNCTIONS --- local getWeapon = function(clientNum) return et.gentity_get(clientNum, "sess.playerWeapon") end local getTeam = function(clientNum) return et.gentity_get(clientNum, "sess.sessionTeam") end local getClass = function(clientNum) return et.gentity_get(clientNum, "sess.playerType") end local setPlayerWeapon = function(clientNum, weapon_id) et.gentity_set(clientNum, "sess.latchPlayerWeapon", weapon_id) end local getLatchedWeapon = function(clientNum) return et.gentity_get(clientNum, "sess.latchPlayerWeapon") end local countSnipers = function(team) if team ~= et.TEAM_AXIS and team ~= et.TEAM_ALLIES then --et.trap_SendServerCommand(-1, "chat \"CountSnipers failed: invalid team\"\n") return 0 end local snipercount = 0 for k=0,MAXCLIENTS-1 do if getTeam(k) == team then if team == et.TEAM_AXIS then if getWeapon(k) == et.WEAPON_K43 or getLatchedWeapon(clientNum) == et.WEAPON_K43 then snipercount = snipercount + 1 end elseif team == et.TEAM_ALLIES then if getWeapon(k) == et.WEAPON_GARAND or getLatchedWeapon(clientNum) == et.WEAPON_GARAND then snipercount = snipercount + 1 end end end end --et.trap_SendServerCommand(-1, "chat \"Sniper count for ".. team .. " is ".. snipercount .. "\"\n") return snipercount end -- returns nil if ok, or reason --- Check userinfo for errors/exploits -- returns nil if ok -- returns reason if error local check_userinfo = function(clientNum) local userinfo = et.trap_GetUserinfo(clientNum) -- bad things can happen if it's full if string.len(userinfo) > 980 then return "oversized" end -- newlines can confuse various log parsers, and should never be there -- note this DOES NOT protect your log parsers, as the userinfo may -- already have been sent to the log if string.find(userinfo,"\n") then return "new line" end -- the game never seems to make userinfos without a leading backslash, -- or with a trailing backslash, so reject those from the start if string.sub(userinfo,1,1) ~= "\\" then return "missing leading slash" end -- shouldn't really be possible, since the engine stuffs ip\ip:port on the end if string.sub(userinfo,-1,1) == "\\" then return "trailing slash" end -- now that we know it is properly formed, count the slashes local n = 0 for _ in string.gfind(userinfo,"\\") do n = n + 1 end if math.mod(n,2) == 1 then return "unbalanced" end local m local t = {} -- right number of slashes, check for dupe keys for m in string.gfind(userinfo, "\\([^\\]*)\\") do if string.len(m) == 0 then return "empty key" end m = string.lower(m) if t[m] then return "duplicate key" end t[m] = true end -- they might hose the userinfo in some way that prevents the ip from being -- obtained. If so -> dump local ip = et.Info_ValueForKey( userinfo, "ip" ) if ip == "" then return "missing ip" end -- make sure whatever is there is roughly valid while we are at it -- "localhost" may be present on a listen server. This module is not intended for listen servers. -- string.match 5.1.x -- string.find 5.0.x if string.find(ip,"^%d+%.%d+%.%d+%.%d+:%d+$") == nil then return "malformed ip" end -- check for this to prevent exploitation of guidcheck -- note the proper solution would be for chats to always have a prefix in the console. -- Why the fuck does the server console need both -- say: [NW]reyalP: blah -- [NW]reyalP: blah local name = et.Info_ValueForKey( userinfo, "name" ) name = et.Q_CleanStr(name) if name == "" or not string.find(name, "%S+") then -- make sure people have some sort of name return "missing name" end for _, badnamepat in ipairs(BADNAMES) do local mstart,mend,cno = string.find(name,badnamepat) if mstart then return "name abuse" end end end local bad_guid = function(clientNum, reason) local bantime = tonumber(et.trap_Cvar_Get( "guidcheck_bantime" )) if not bantime or bantime < 0 then bantime = DEF_GUIDCHECK_BANTIME end et.G_LogPrint(string.format("guidcheck: client %d bad GUID %s\n", clientNum, reason)) et.trap_DropClient(clientNum, "You are banned from this server", bantime) end local check_guid_line = function(text) local guid,netname local mstart,mend,clientNum = string.find(text,"^etpro IAC: (%d+) GUID %[") if not mstart then return end text=string.sub(text,mend+1) --GUID] [NETNAME]\n mstart,mend,guid = string.find(text,"^([^%]]*)%] %[") if not mstart then bad_guid(clientNum, "couldn't parse guid") return end --NETNAME]\n text=string.sub(text,mend+1) netname = et.gentity_get(clientNum,"pers.netname") mstart,mend = string.find(text,netname,1,true) if not mstart or mstart ~= 1 then bad_guid(clientNum,"couldn't parse name") return end text=string.sub(text,mend+1) if text ~= "]\n" then bad_guid(clientNum, "trailing garbage") return end et.G_Printf("guidcheck: etpro GUID %d %s %s\n",clientNum,guid,netname) -- {N} is too complicated! mstart,mend = string.find(guid,"^%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x$") if not mstart then bad_guid(clientNum,"malformed") return end end local IPForClient = function(clientNum) local userinfo = et.trap_GetUserinfo( clientNum ) if userinfo == "" then return "" end local ip = et.Info_ValueForKey( userinfo, "ip" ) -- find IP and strip port local ipstart, ipend, ipmatch = string.find(ip,"(%d+%.%d+%.%d+%.%d+)") -- don't error out if we don't match an ip if not ipstart then return "" end return ipmatch end local parseString = function(inputString) local i = 1 local t = {} for w in string.gfind(inputString, "([^%s]+)%s*") do t[i]=w i=i+1 end return t end function getCS() local cs = et.trap_GetConfigstring(et.CS_VOTE_STRING) local t = ParseString(cs) return t[4] end et.G_Printf = function(...) et.G_Print(string.format(unpack(arg))) end --- END LOCAL HELPER FUNCTIONS --- --- CALLBACK FUNCTIONS --- function et_InitGame(levelTime, randomSeed, restart) et.RegisterModname(MODNAME .. " " .. VERSION) CONFIG = et.trap_Cvar_Get("b_config") GAMEFORMAT = et.trap_Cvar_Get("b_gameformat") GAMESTATE = tonumber(et.trap_Cvar_Get("gamestate")) MAXCLIENTS = tonumber(et.trap_Cvar_Get("sv_maxclients")) SNIPER_AXIS = 0 SNIPER_ALLIES = 0 for k=0,MAXCLIENTS-1 do clientSprint[k] = 0 end end function et_RunFrame(levelTime) -- give clients that just spawned infinte sprint for very small time t -- hopefully fixed a bug where you spawn with no sprint for k=0,MAXCLIENTS-1 do if clientSprint[k] == 1 then et.gentity_set(k, "ps.powerups", 12, levelTime + 50 ) end clientSprint[k] = 0 end if (IC_LAST + IC_FREQ) > levelTime then return end IC_LAST = levelTime if et.gentity_get( IC_CLIENT, "inuse" ) == 1 then local reason = check_userinfo( IC_CLIENT ) if reason then et.G_LogPrint(string.format("userinfocheck frame: client %d bad userinfo %s\n", IC_CLIENT, reason)) et.trap_SetUserinfo(IC_CLIENT, "name\\badinfo") et.trap_DropClient(IC_CLIENT, "bad userinfo", 0) end end IC_CLIENT = IC_CLIENT + 1 if IC_CLIENT >= MAXCLIENTS then IC_CLIENT = 0 end end function et_ClientUserinfoChanged(clientNum) local reason = check_userinfo( clientNum ) if reason then et.G_LogPrint(string.format("userinfocheck infochanged: client %d bad userinfo %s\n", clientNum, reason)) et.trap_SetUserinfo( clientNum, "name\\badinfo" ) et.trap_DropClient( clientNum, "bad userinfo", 0 ) end end function et_Print(text) check_guid_line(text) local t = parseString(text) if t[2] == "Passed:" and t[4] == "map" then if string.find(t[6], "%u") == nil or t[6] ~= getCS() then return 1 end local mapname = string.lower(t[6]) et.trap_SendConsoleCommand( et.EXEC_APPEND, "wait ;ref map " .. mapname .. ";wait 100;ref config " .. CONFIG .. "\n" ) end end function et_ClientConnect(clientNum, firstTime, isBot) -- userinfocheck stuff. Do this before IP limit -- printf("connect %d\n",cno) local reason = check_userinfo( clientNum ) if reason then et.G_LogPrint(string.format("userinfocheck connect: client %d bad userinfo %s\n", clientNum, reason)) return "bad userinfo" end -- note IP validity should be enforced by userinfocheck stuff local ip = IPForClient( clientNum ) local count = 1 -- we count as the first one local max = tonumber(et.trap_Cvar_Get( "ip_max_clients" )) if not max or max <= 0 then max = DEF_IP_MAX_CLIENTS end -- it's probably safe to only do this on firsttime, but checking -- every time doesn't hurt much -- validate userinfo to filter out the people blindly using luigi's code local userinfo = et.trap_GetUserinfo( clientNum ) if et.Info_ValueForKey( userinfo, "rate" ) == "" then et.G_Printf(MODNAME .. ": invalid userinfo from %s\n", ip) return "invalid connection" end for i = 0, MAXCLIENTS - 1 do -- pers.connected is set correctly for fake players -- can't rely on userinfo being empty if i ~= clientNum and et.gentity_get(i,"pers.connected") > 0 and ip == IPForClient(i) then count = count + 1 if count > max then et.G_Printf(MODNAME .. ": too many connections from %s\n", ip) return string.format("only %d connections per IP are allowed on this server", max) end end end end function et_ClientSpawn(clientNum, revived) if revived == 0 then clientSprint[clientNum] = 1 end if GAMEFORMAT == "5" then if revived == 0 and et.gentity_get(clientNum, "sess.playerType") == 2 then et.gentity_set(clientNum, "ps.ammo", 39, 3) et.gentity_set(clientNum, "ps.ammo", 40, 3) end end if GAMESTATE == 2 then if tonumber(GAMEFORMAT) < 5 then -- disable rifle and soldier if getClass(clientNum) == et.CLASS_SOLDIER then et.gentity_set(clientNum, "sess.latchPlayerType", et.CLASS_MEDIC) et.gentity_set(clientNum, "health", -200) end if getWeapon(clientNum) == et.WEAPON_GARAND_NADE then et.gentity_set(clientNum, "sess.latchPlayerType", et.CLASS_ENGINEER) et.gentity_set(clientNum, "sess.latchPlayerWeapon", et.WEAPON_THOMPSON) et.gentity_set(clientNum, "health", -200) end if getWeapon(clientNum) == et.WEAPON_K43_NADE then et.gentity_set(clientNum, "sess.latchPlayerType", et.CLASS_ENGINEER) et.gentity_set(clientNum, "sess.latchPlayerWeapon", et.WEAPON_MP40) et.gentity_set(clientNum, "health", -200) end elseif tonumber(GAMEFORMAT) == 5 then -- disable mg if getClass(clientNum) == et.CLASS_SOLDIER and getWeapon(clientNum) == et.WEAPON_MG42 then et.gentity_set(clientNum, "sess.latchPlayerType", et.CLASS_MEDIC) et.gentity_set(clientNum, "health", -200) end end end end function et_ClientCommand(clientNum) local cmd = string.lower(et.trap_Argv(0)) --- Forcetapout bug fix if cmd == "forcetapout" then if et.gentity_get(clientNum, "r.contents") == 0 then -- contents 0 == nobody return 1 --prevent it end end --- End forcetapout --- Limit snipers for lower than 6 format --[[local ignorecmds = { say=true, obj=true, score=true, ws=true, vote=true, say_team=true, say_buddy=true, kill=true, forcetapout=true, userinfo=true, fu=true, }--]] if GAMEFORMAT ~= "6" then -- gameformat check local arg1 = string.lower(et.trap_Argv(1)) local arg2 = string.lower(et.trap_Argv(2)) local arg3 = string.lower(et.trap_Argv(3)) local team = getTeam(clientNum) --et.trap_SendServerCommand(-1, "chat \"cmd:(" .. et.ConcatArgs(0) .. ")\"\n") -- CLASS COMMAND (Not initaited?) --[[if ( cmd == "class" and arg1 == "c" and arg2 == "3" ) then if countSnipers(team) > 1 then setPlayerWeapon(clientNum, 10) return 1 end return 0 end--]] -- TEAM COMMAND if ( cmd == "team" and arg1 == "r" and arg2 == "4" and arg3 == "32" ) then -- team axis covert k43 --et.trap_SendServerCommand(-1, "chat \"Count axis snipers\"\n") if countSnipers(et.TEAM_AXIS) >= 1 then setPlayerWeapon(clientNum, et.WEAPON_STEN) --et.trap_SendServerCommand(-1, "chat \"Axis forced weaponswitch\"\n") et.trap_SendServerCommand(clientNum, "cp \"^3Maximum amount of snipers already on the team\"\n") return 1 end return 0 end if ( cmd == "team" and arg1 == "b" and arg2 == "4" and arg3 == "25" ) then -- team allies covert garand if countSnipers(et.TEAM_ALLIES) >= 1 then setPlayerWeapon(clientNum, et.WEAPON_STEN) --et.trap_SendServerCommand(-1, "chat \"Allied forced weaponswitch\"\n") et.trap_SendServerCommand(clientNum, "cp \"^3Maximum amount of snipers already on the team\"\n") return 1 end return 0 end end -- gameformat check end if cmd == "ws" then local n = tonumber(arg1) if not n then et.G_LogPrint(string.format("wsfix: client %d bad ws not a number [%s]\n",clientNum,tostring(arg1))) return 1 end if n < 0 or n > 21 then et.G_LogPrint(string.format("wsfix: client %d bad ws %d\n",clientNum,n)) return 1 end return 0 end if cmd == "callvote" or cmd == "ref" or cmd == "sa" or cmd == "semiadmin" then local args = et.ConcatArgs(1) if string.find(args,"[\r\n]") then et.G_LogPrint(string.format("combinedfixes: client %d bad %s [%s]\n",clientNum,cmd,args)) return 1 end return 0 end return 0 end --- END CALLBACK FUNCTIONS ---