minetest-mod-governing/anticheats.lua

927 lines
36 KiB
Lua

-------------------------------------------------------------------------
-- ANTI PRIVS detects if a custom client without interacting "zips" around the map
-- Copyright (c) 2021 mt-mods/BuckarooBanzay MIT
-- Copyright 2020-2023 improvements and few fixes, mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
-- per-player data
local player_data = {}
local is_player = function(player)
-- a table being a player is also supported because it quacks sufficiently
-- like a player if it has the is_player function
local t = type(player)
return (t == "userdata" or t == "table") and type(player.is_player) == "function"
end
-- obtain player data cheats, improved respect beowulf with missing checks and methods
local function get_player_data(name)
local player = minetest.get_player_by_name(name)
if not player_data[name] then
player_data[name] = {
fliyers = 0, -- number of checks for fly ( added only with governing)
strikes = 0, -- number of "strikes" (odd movements)
checked = 0, -- number of checks
pre_pos = player:get_pos() -- position track (missing in beowulf as bug)
}
end
return player_data[name]
-- WARNING pos its always current so checked must be after an interval using callbacks
end
-- clear player data for cheats
local function track_player_clear(player)
if is_player(player) then
if player:get_player_name() then
player_data[player:get_player_name()] = nil
end
end
end
-- store player data cheats strikes and checks
local function track_player(player)
if not is_player(player) then return end
local name = player:get_player_name()
local data = get_player_data(name)
local pos = player:get_pos()
if data.pre_pos then
-- compare positions
local d = vector.distance(pos, data.pre_pos)
if d > 200 then
data.strikes = data.strikes + 1
end
data.checked = data.checked + 1
end
if data.checked >= 10 then
-- check strike-count after 10 movement checks
if data.strikes > 8 then
-- suspicious movement, log it
-- TODO: if this doesn't yield any false-positives, add a kick/ban option
local msg = "suspicious movement detected for player: '" .. name .. "'"
minetest.log("action", "[governing][beowulf] " .. msg)
end
-- reset counters
data.checked = 0
data.strikes = 0
end
-- store current position
data.pre_pos = pos
end
-----------------------------------------------------------------------
-- ANTI CRACKERS and CHEATERS by mckaygerhard based on beowulf
------------------------------------------------------------------------
-- luacheck: push no max line length
local df = {
"2.0",
"dragonfire", -- many based clients by example teamclient
"f8fd5c11b", -- Merge pull request #59 from PrairieAstronomer/readme_irrlicht_change
"c66ae6717", -- Add exact irrlichtmt version to the clone command
"296cce39d", -- Fix upstream merge issues
"350b6d175", -- Install lua_async dependency
"393c83928", -- Don't include client/game.h on server build
"ccd4c504d", -- Ship dragonfireclient with selected high-quality mods
"147aaf326", -- Fix build instructions
"b09fc5de5", -- Add spider
"d404517d2", -- Make LuaVoxelManipulator available to CSM API
"1ccf88e80", -- minetest.dig_node: Remove node
"950d2c9b3", -- Add ClientObjectRef:remove and return true in on_object_add callback to remove newly added object
"fb4815c66", -- Merge pull request #35 from arydevy/patch-1
"f12288814", -- Merge pull request #42 from Minetest-j45/master
"f3082146c", -- remove irrlicht from lid dir (accident)
"a3925db22", -- add airjump and remove unused headers
"7824a4956", -- Merge pull request #1 from EliasFleckenstein03/master
"35445d24f", -- Make set_pitch and set_yaw more accurate by not rounding it to integers
"5131675a6", -- Add guards to stop server build fail
"96a37aed3", -- Add minetest.get_send_speed
"d08242316", -- Fix format
"ce0d81a82", -- Change default cheat menu entry height
"b7abc8df2", -- Add on_object_add callback
"4f613bbf5", -- Include tile definitions in get_node_def; Client-side minetest.object_refs table
"c86dcd0f6", -- Add on_object_hp_change callback and nametag images
"b84ed7d0b", -- Call on_object_properties_change callback when adding object to scene
"26cfbda65", -- Add on_object_properties_change callback
"6dc7a65d9", -- Add ClientObjectRef:set_properties
"7d7d4d675", -- Add ClientObjectRef.get_properties
"ea8fa30b6", -- Changed README.md to fit the dragonfire client
"c47eae316", -- Add table.combine to luacheckrc
"83d09ffaf", -- Complete documentation
"e0b4859e7", -- Add ClientObjectRef:remove
"63f7c96ec", -- Fix legit_speed
"5c06763e8", -- Add noise to client CSM API
"7613d9bfe", -- Update .wielded command to output the entire itemstring; add LocalPlayer:get_hotbar_size
"bc79c2344", -- CSM: Use server-like (and safe) HTTP API instead of Mainmenu-like
"166968232", -- Port formspec API from waspsaliva This API is inofficial and undocumented; invalid usage causes the game to crash. Use at own risk!
"e391ee435", -- Forcefully place items when minetest.place_node is used
"546ab256b", -- Update buildbot to new MineClone2 repo and set the game name to MineClone2 rather than mineclone2
"d3780cefd", -- Attempt to fix SEGFAULT in push_inventory
"d1c84ada2", -- Merge minetest changes
"74f5f033e", -- Add Custom version string
"b2f629d8d", -- Logo improvements
"78b7d1019", -- Add dragonfire logo
"19e0528e3", -- Add minetest.get_nearby_objects
"47d0882cc", -- Fix line containing only whitespace
"4fedc3a31", -- Add minetest.interact
"dc67f669e", -- Make the Cheat Menu size configureable
"906845a87", -- Add minetest.registered_items and minetest.registered_nodes (Doesn't do anything yet)
"3a4325902", -- Fixed crash by adding legacy stuff to defaultsettings (for now)
"53c991c5f", -- Fixed crash due to missing entry in defaultsettings.cpp
"0c6e0c717", -- Reorganize categories
"e8faa2afb", -- Rework Range
"a4d914ba2", -- Make GitHub Actions Happy try 3
"a34c61093", -- Make GitHub Actions Happy try 2
"f783f5939", -- Make GitHub Actions Happy try 1
"8b58465aa", -- Remove obsolete code from clientenvironment
"35c15567a", -- Update builtin/settingtypes.txt to the new philosophy
"0c9e7466e", -- New Cheat Philosophy
"a1e61e561", -- World Cheats improvements; Add BlockLava; Readd minetest.request_http_api for Compatibility
"56d536ea5", -- Update CheatDB URL again
"ce47003cc", -- Update defaults for ContentDB (->CheatDB)
"89995efee", -- CheatDB Support & Enable/Disable CSMs in Main Menu
"3df23e23c", -- Small AutoTool Fix
"8b3eaf5b0", -- Lua API: Particle callbacks; Add NoWeather
"0a285dd33", -- Remove NextItem
"4695222bc", -- Fix and Improve AutoTool
"5bead7daa", -- Added minetest.close_formspec
"f825cf0e3", -- Fixed Minimap position
"eaa8a5132", -- Fixed FastPlace and AutoPlace
"b4e475726", -- Added configureable Colors for PlayerESP and EntityESP
"549025f6a", -- EntityESP, EntityTracers, PlayerESP, PlayerTracers
"eb6aca8b4", -- Merged Minetest
"8de51dae9", -- Fixed crash when attempting to access nonexistant inventory from Lua API
"a65251a7a", -- Fixed glowing GenericCAOs being rendered completely back when Fullbright is enabled
"eaec3645b", -- Added ClientObjectRef:get_hp()
"fb4d54ee3", -- Added minetest.register_on_play_sound
"50629cc6a", -- Improved Scaffold
"3d74e17cc", -- Added AutoSlip (-> Credit to Code-Sploit)
"f9c632466", -- Added JetPack and AutoHit (-> Credits to Code-Sploit and cora)
"843239c0b", -- Added Speed/Jump/Gravity Override
"598e9bdbc", -- Update Credits
"7d327def8", -- Improved AutoSneak
"82216e147", -- LocalPlayer:set_physics_override; minetest.register_on_recieve_physics_override
"4dd5ecfc5", -- Added setpitch & setyaw commands; AutoSprint
"b65db98bd", -- Added OnlyTracePlayers
"e16bbc1fb", -- Merge pull request #14 from corarona/master
"1780adeea", -- lua-api: fix get/set_pitch
"3e7c5d720", -- Possibility to use cheat menu while pressing other keys
"0aa63aafc", -- Fixed warning
"9db80fc6f", -- Run Lint Script
"91ad0d049", -- Merge pull request #10 from corarona/master
"6bda686c0", -- MapBlockMesh Performance Improvement
"1bab49049", -- add LUA_FCT
"6efa8a758", -- add g/s pitch and make_screenshot in lua api
"46237330d", -- Several Enhancements
"60a9ff6ff", -- api-screenshot: change function name to make_screenshot
"1f56317d5", -- Added NodeESP
"75ecaa217", -- Fix and run the Lint autocorrect script
"6ccb5835f", -- Revert "Make Lint Happy"
"244713971", -- Added script that automaticall corrects lint style
"07e61e115", -- Fix github build problems #3
"3af10766f", -- Fix github build problems #2
"16d302c9a", -- Fix github build problems
"ad148587d", -- Make Lint Happy
"1145b05ea", -- Updated Credits
"c9221730d", -- Updated Cheat Menu Color Design
"1799d5aa9", -- Cheat Menu Improvements Change
"fba7dc216", -- Merge pull request #8 from realOneplustwo/master
"a7dc1135e", -- Added CheatHUD
"f1d9ac014", -- Moved Killaura to Lua; Added ForceField; Added Friendlist; Added ClientObjectRef:is_local_player(); Documented LocalPlayer:get_object()
"06b72069d", -- Fixed ColorChat
"62958bd60", -- Reverted accidental commit in wrong repo
"00d51fbd5", -- Armor textures support
"7cbe42b1d", -- Re-Added Chat Effects
"28f6a7970", -- lua api: add set/get_pitch
"4f9797b6e", -- lua api: add core.take_screenshot()
"8e9e76a50", -- Revert "Add Block Formspec Hack"
"6652d7ac2", -- Add Block Formspec Hack
"8bc7d49b3", -- Added Nuke
"62cf9b466", -- Fix compile error
"519f98c65", -- Merge pull request #3 from JosiahWI/ui_revamp
"f236476af", -- Fix errors in cheatMenu.
"7af3dee31", -- Merge pull request #2 from JosiahWI/ui_revamp
"ea88dde4b", -- Added Strip, AutoRefill, indexing for InventoryActions and Wield Index starts at 1 now
"7aff09ab2", -- Fix overindent!
"aea9b36ef", -- Improved Colours
"1ef72ad9c", -- Fix indentation style.
"586241008", -- Add missing return.
"b211e90ff", -- Prepare cheatMenu::draw function for easier UI changes.
"e22e334e9", -- Merge pull request #1 from JosiahWI/ui_revamp
"f605308ee", -- Improve drawEntry.
"130d476f6", -- Changed Cheat Menu UI
"f1ff05bf5", -- Added ThroughWalls, InventoryActions API and AutoTotem
"1a7d3d818", -- Extended ClientObjectRef; Improved CrystalPvP
"1e4f35492", -- This is the last try for lint...
"7e0f8fba0", -- Another Lint commit
"151e5782e", -- Lint is still not happy...
"28a560684", -- Added the API additions from waspsaliva
"c1aea404b", -- Lint is bitch
"3a718f12b", -- Make lint happy; Remove stupid redirector
"3b596a96e", -- Fixed Github build problems
"847198edb", -- Edited .gitignore properly; fixed armor invulnarability in the server code.
"bbcd24954", -- New Mod System
"80f416d51", -- Added AttachmentFloat
"cb1915efa", -- Added minetest.drop_selected_item(), Improved AutoEject
"43ee069db", -- Improved X-Ray, added AutoEject
"faa32610e", -- Added ESP, fixed Tracers, improved Jesus
"ee88f4b94", -- Improved Tracers
"c36ff3edb", -- Added AutoSneak and improved X-Ray MapBlock updating
"0a2c90f4c", -- Only draw tracers to objects that are not attached (that fixes tracers to armor)
"044a12666", -- Added Tracers, NoSlow and NoForceRotate; GUI Colors changed
"b9f8f0a23", -- The Robot Update
"af085acbd", -- Added Schematicas
"0730ed216", -- Delete my stupid test mod lol
"772c9629e", -- Unrestricted HTTP API for Client, Server and Main Menu
"9b1030cac", -- Added minetest.get_inventory(location)
"2321e3da4", -- Removed console output spammed by minetest.find_node_near
"90f66dad8", -- Removed experimental code
"8b4d27141", -- Fixed typo in clientmods/inventory/mod.conf
"d8b8c1d31", -- Added Documentation for Additional API
"6e6c68ba0", -- Added Chat Spam, Replace and settingtypes.txt for Clientmods
"79d0314d7", -- Update Buildbots
"770bde9c6", -- idk
"e245151c5", -- Improved World hacks
"19205f6b3", -- Improved World Hacks, added API functions
"73b89703f", -- Improved World hacks, added fill
"248dedaba", -- Added floationg water to BlockWater
"9dc3eb777", -- Fixed broken Chatcommands
"80371bc16", -- Added .listwarps
"1c29f21e0", -- Imporoved set_wield_index() to include camera update
"3bed0981d", -- UI Update; Added AutoTool
"622d54726", -- Added DestroyWater (:P anon)
"9019e18b9", -- Some Updates
"107dec6c0", -- Added Coords
"f1760622e", -- Added BrightNight
"2675bcca1", -- Added more cheats
"3d980cf57", -- Improved Xray and Fullbright
"f7a042223", -- Added cheat Menu
"344fddc17", -- Improved Killaura and Chat position
"678559bb6", -- removed leagcy clientmods
"9194165cf", -- Added autodig, moved chat
"064c25caa", -- Added EntitySpeed
"5a8610c2f", -- Added customizable keybindings, improved freecam, added special inventory keybind (by default ender inventory)
"83f59484d", -- Fixed 5.4.0-dev build
"ffe3c2ae0", -- Update to minetest 5.4.0-dev
"45aa2516b", -- Added settings
"f22339ed8", -- Removed fast killaura
"408e39a1d", -- Added Anti Knockback
"eee0f960b", -- Removed minetest.conf.old
"39d7567c1", -- Fixed Crash
"305e0e0d3", -- Auto disable smooth lighting when fullbright is active
"6796baec6", -- Defaultsettings
"5a2bf6634", -- Added Clientmods
"e610149c0", -- Initial Commit
"68f9263a2", -- Hacked Client
"90d885506", -- GalwayGirl Client
}
--[[
To update commit list: save commit logs for master branches of minetest in mt.log and dragonfire in df.log
$ git log --oneline origin/master > mt.log; git log --oneline dragonfire/master > df.log (both in same remo using git remote)
Find and drop commits that seems to exist in both remotes, print rest of it:
$ sort -k3 <(awk '{print NR" "$0}' df.log mt.log mt.log) | uniq -uf2 | sort -nk1 | sed -r 's/^[^ ]+ ([^ ]+) (.*)$/\t"\1", -- \2/'
Replace above entries with output
--]]
local function dfver(stringversion)
-- loop into table of commmits and find the compared version
for _,v in ipairs(df) do
local pp = stringversion:find(v)
if pp then
return v
end
end
end
-------------------------------------------------------------------------
-- ANTI CHEAT for MT4 and old MT5 engines by rnd
-- Copyright 2016 rnd LGPL v3
-- Copyright 2020-2023 improvements and few fixes, mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
local cheat = {};
local version = "09/08/2017";
anticheatsettings = {};
anticheatsettings.moderators = {}
anticheatsettings.CHEAT_TIMESTEP = tonumber(minetest.settings:get("governing.timestep")) or 15; -- check timestep all players
anticheatsettings.CHECK_AGAIN = tonumber(minetest.settings:get("governing.timeagain")) or 15; -- after player found in bad position check again after this to make sure its not lag, this should be larger than expected lag in seconds
anticheatsettings.STRING_MODERA = minetest.settings:get("governing.moderators") or "admin,singleplayer";
-- moderators list, those players can use cheat debug and will see full cheat message
for str in string.gmatch(anticheatsettings.STRING_MODERA, "([^,]+)") do table.insert(anticheatsettings.moderators, str) end
local CHEAT_TIMESTEP = anticheatsettings.CHEAT_TIMESTEP;
local CHECK_AGAIN = anticheatsettings.CHECK_AGAIN;
cheat.moderators = anticheatsettings.moderators;
bonemod = minetest.get_modpath("boneworld")
anticheatdb = {}; -- data about detected cheaters
cheat.suspect = "";
cheat.players = {}; -- temporary cheat detection db
cheat.message = "";
cheat.debuglist = {}; -- [name]=true -- who gets to see debug msgs
cheat.scan_timer = 0; -- global scan of players
cheat.stat_timer = 0; -- used to collect stats
cheat.nodelist = {};
cheat.timestep = CHEAT_TIMESTEP;
-- list of forbidden nodes
cheat.nodelist = {["default:stone"] = false, ["default:cobble"]= false, ["default:dirt"] = false, ["default:sand"]=false,["default:tree"]= false};
local punish_cheat = function(name)
local player = minetest.get_player_by_name(name);
local ip = tostring(minetest.get_player_ip(name));
if not player then return end
local text=""; local logtext = "";
if cheat.players[name].cheattype == 1 then
text = "#anticheat: ".. name .. " was caught walking inside wall";
logtext = os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught walking inside wall at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
--player:set_hp(0);
elseif cheat.players[name].cheattype == 2 then
local gravity = player:get_physics_override().gravity; if gravity<1 then return end
logtext= os.date("%H:%M.%S").." #anticheat: ".. name .. " was caught flying at " .. minetest.pos_to_string(cheat.players[name].cheatpos);
if cheat.players[name].cheatpos.y>5 then -- only above height 5 it directly damages flyer
text = "#anticheat: ".. name .. " was caught flying";
--player:set_hp(0);
end
end
if text~="" then
minetest.chat_send_all(text);
end
if logtext~="" then
minetest.log("action", logtext);
cheat.message = logtext;
anticheatdb[ip] = {name = name, msg = logtext};
cheat.players[name].count=0; -- reset counter
cheat.players[name].cheattype = 0;
for namem,_ in pairs(cheat.moderators) do -- display full message to moderators
minetest.chat_send_player(namem,logtext);
end
end
end
-- CHECKS
-- DETAILED NOCLIP CHECK
local check_noclip = function(pos)
local nodename = minetest.get_node(pos).name;
local clear=true;
if nodename ~= "air" then -- check if forbidden material!
clear = cheat.nodelist[nodename]; -- test clip violation
if clear == nil then clear = true end
end
if not clear then -- more detailed check
local anodes = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y+1, z=pos.z+1}, {"air"});
if #anodes == 0 then return false end
clear=true;
end
return clear;
end
-- DETAILED FLY CHECK
local check_fly = function(pos) -- return true if player not flying
local fly = (minetest.get_node(pos).name=="air" and minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name=="air"); -- prerequisite for flying is this to be "air", but not sufficient condition
if not fly then return true end;
local anodes = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, {"air"});
if #anodes == 18 then -- player standing on air?
return false
else
return true
end
end
local round = function (x)
if x > 0 then
return math.floor(x+0.5)
else
return -math.floor(-x+0.5)
end
end
--main check routine
local check_player = function(player)
local name = player:get_player_name();
local privs = minetest.get_player_privs(name).kick;if privs then return end -- dont check moderators
local pos = player:getpos(); -- feet position
pos.x = round(pos.x*10)/10;pos.z = round(pos.z*10)/10; -- less useless clutter
pos.y = round(pos.y*10)/10; -- minetest buggy collision - need to do this or it returns wrong materials for feet position: aka magic number 0.498?????228
if pos.y<0 then pos.y=pos.y+1 end -- weird, without this it fails to check feet block where y<0, it checks one below feet
local nodename = minetest.get_node(pos).name;
local clear=true;
if nodename ~= "air" then -- check if forbidden material!
clear = cheat.nodelist[nodename]; -- test clip violation
if clear == nil then clear = true end
end
local fly = (nodename=="air" and minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name=="air"); -- prerequisite for flying, but not sufficient condition
if cheat.players[name].count == 0 then -- player hasnt "cheated" yet, remember last clear position
cheat.players[name].clearpos = cheat.players[name].lastpos
end
-- manage noclip cheats
if not clear then -- player caught inside walls
local moved = (cheat.players[name].lastpos.x~=pos.x) or (cheat.players[name].lastpos.y~=pos.y) or (cheat.players[name].lastpos.z~=pos.z);
if moved then -- if player stands still whole time do nothing
if cheat.players[name].count == 0 then cheat.players[name].cheatpos = pos end -- remember first position where player found inside wall
if cheat.players[name].count == 0 then
minetest.after(CHECK_AGAIN+math.random(5),
function()
cheat.players[name].count = 0;
if not check_noclip(pos) then
punish_cheat(name)-- we got a cheater!
else
cheat.players[name].count = 0; -- reset
cheat.players[name].cheattype = 0;
end
end
)
end
if cheat.players[name].count == 0 then -- mark as suspect
cheat.players[name].count = 1;
cheat.players[name].cheattype = 1;
end
end
end
-- manage flyers
if fly then
local fpos;
fly,fpos = minetest.line_of_sight(pos, {x = pos.x, y = pos.y - 4, z = pos.z}, 1); --checking node maximal jump height below feet
if fly then -- so we are in air, are we flying?
if player:get_player_control().sneak then -- player sneaks, maybe on border?
--search 18 nodes to find non air
local anodes = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y-1, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, {"air"});
if #anodes < 18 then fly = false end
end -- if at this point fly = true means player is not standing on border
if pos.y>=cheat.players[name].lastpos.y and fly then -- we actually didnt go down from last time and not on border
-- was lastpos in air too?
local lastpos = cheat.players[name].lastpos;
local anodes = minetest.find_nodes_in_area({x=lastpos.x-1, y=lastpos.y-1, z=lastpos.z-1}, {x=lastpos.x+1, y=lastpos.y, z=lastpos.z+1}, {"air"});
if #anodes == 18 then fly = true else fly = false end
if fly then -- so now in air above previous position, which was in air too?
if cheat.players[name].count == 0 then cheat.players[name].cheatpos = pos end -- remember first position where player found "cheating"
if cheat.players[name].count == 0 then
minetest.after(CHECK_AGAIN,
function()
cheat.players[name].count = 0;
if not check_fly(pos) then
punish_cheat(name)-- we got a cheater!
else
cheat.players[name].count = 0;
cheat.players[name].cheattype = 0;
end
end
)
end
if cheat.players[name].count == 0 then -- mark as suspect
cheat.players[name].count = 1;
cheat.players[name].cheattype = 2;
end
end
end
end
end
cheat.players[name].lastpos = pos
end
minetest.register_globalstep(function(dtime)
cheat.scan_timer = cheat.scan_timer + dtime
-- GENERAL SCAN OF ALL PLAYERS
if cheat.scan_timer>cheat.timestep then
cheat.stat_timer = cheat.stat_timer + cheat.timestep;
-- dig xp stats every 2 minutes
if bonemod and cheat.stat_timer>120 then
cheat.stat_timer = 0;
local players = minetest.get_connected_players();
for _,player in pairs(players) do
local pname = player:get_player_name();
if cheat.players[pname].stats.state == 1 then -- only if dig xp loaded to prevent anomalous stats
if boneworld.digxp[pname] then
local deltadig = cheat.players[pname].stats.digxp;
cheat.players[pname].stats.digxp = boneworld.digxp[pname];
deltadig = boneworld.digxp[pname]-deltadig;
cheat.players[pname].stats.deltadig = deltadig;
if deltadig>cheat.players[pname].stats.maxdeltadig then
cheat.players[pname].stats.maxdeltadig = deltadig;
end
if deltadig>2 then -- unnaturally high deltadig
local ip = tostring(minetest.get_player_ip(pname));
local logtext = os.date("%H:%M.%S") .. " #anticheat: " .. pname .. " (ip " .. ip .. ") is mining resources too fast, deltadig " .. deltadig;
anticheatdb[ip] = {name = pname, msg = logtext};
minetest.log("action", logtext);
end
end
end
end
end
cheat.timestep = CHEAT_TIMESTEP + (2*math.random()-1)*2; -- randomize step so its unpredictable
cheat.scan_timer=0;
--local t = minetest.get_gametime();
local players = minetest.get_connected_players();
for _,player in pairs(players) do
check_player(player);
end
for name,_ in pairs(cheat.debuglist) do -- show suspects in debug
for _,player in pairs(players) do
local pname = player:get_player_name();
if cheat.players[pname].count>0 then
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].clearpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
end
end
end
end
end)
-- long range dig check
local check_can_dig = function(pos, digger)
local p = digger:getpos();
if p.y<0 then p.y=p.y+2 else p.y=p.y+1 end -- head position
local dist = math.max(math.abs(p.x-pos.x),math.abs(p.y-pos.y),math.abs(p.z-pos.z));
if dist>6 then -- here 5
dist = math.floor(dist*100)/100;
local pname = digger:get_player_name();
local logtext = os.date("%H:%M.%S") .. "#anticheat: long range dig " .. pname ..", distance " .. dist .. ", pos " .. minetest.pos_to_string(pos);
for name,_ in pairs(cheat.debuglist) do -- show to all watchers
minetest.chat_send_player(name,logtext)
minetest.log("warning", "[governing] "..logtext)
end
local ip = tostring(minetest.get_player_ip(pname));
anticheatdb[ip] = {name = pname, msg = logtext};
return false
end
return true
end
local set_check_can_dig = function(name)
local tabl = minetest.registered_nodes[name];
if not tabl then return end
tabl.can_dig = check_can_dig;
minetest.override_item(name, {can_dig = check_can_dig})
--minetest.register_node(":"..name, tabl);
end
local function is_player(player)
if player then
if type(player) == "userdata" or type(player) == "table" then return true end
end
return false
end
minetest.register_on_joinplayer(function(player)
-- init stuff on player join
if not is_player(player) then return end
local name = player:get_player_name();
if type(name) ~= "string" then return end
local pos = player:getpos();
-- no matter if are incomplete info, save most possible of and start recolection of stats
if cheat.players[name] == nil then
cheat.players[name]={count=0,cheatpos = pos, clearpos = pos, lastpos = pos, cheattype = 0}; -- type 0: none, 1 noclip, 2 fly
end
-- try to fill some stats or retrieve previously
if cheat.players[name] and cheat.players[name].stats == nil then
cheat.players[name].stats = {maxdeltadig=0,deltadig = 0,digxp = 0, state = 0}; -- various statistics about player: max dig xp increase in 2 minutes, current dig xp increase
if bonemod then
minetest.after(4, -- load digxp after boneworld loads it
function()
if boneworld.xp then
cheat.players[name].stats.digxp = boneworld.digxp[name] or 0;
cheat.players[name].stats.state = 1;
end
end
) --state 0 = stats not loaded yet
end
end
-- check anticheat db for cheaters clients
-- ===================================
local ip = tostring(minetest.get_player_ip(name));
local info = minetest.get_player_information(name)
local msgiplevelone = "";
local msgipleveltwo = "";
--check ip first try of info manually, later from player info
if anticheatdb[ip] then
msgiplevelone = "#anticheat: welcome back detected cheater, ip = " .. ip .. ", name " .. anticheatdb[ip].name .. ", new name = " .. name;
end;
--check names from stats
for ip,v in pairs(anticheatdb) do
if v.name == name then
msgiplevelone = "#anticheat: welcome back detected cheater, ip = " .. ip .. ", name = newname = " .. v.name;
break;
end
end
-- send detection msg before try to check info (cos info may be incomplete detection)
if msgiplevelone~="" then
minetest.after(1, function()
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgiplevelone);
end
end)
end
-- rare case / simple sanity check, _should_ not happen ;) pretty rare but modified client that does not give info
if not info then
if not is_player(player) then
name = name .. " is a bot confirmed, also"
end
msgipleveltwo = "[governing] can't retrieve info, player '" .. name .. "' disappeared or seems craker"
minetest.after(2, function()
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgipleveltwo);
end
end)
minetest.log("warning", msgipleveltwo)
return
end
-- prepare information report log, "version_string" is only available if the engine was compiled in debug mode (or patched accordingly)
local msgipleveltwo = "[governing] player '" .. name .. "' joined" ..
" from:" .. info.address ..
" protocol_version:" .. (info.protocol_version or "20?") ..
" formspec_version:" .. (info.formspec_version or "old") ..
" lang_code:" .. (info.lang_code or "<unknown>") ..
" serialization_version:" .. (info.serialization_version or "20?") ..
" version_string:" .. (info.version_string or "not supported")
minetest.log("action", msgipleveltwo)
local msgm = "#anticheat detected a cheater with ".. tostring(minetest.get_player_ip(name)).." named "..name
if info.version_string then
local dfv = dfver(info.version_string)
if dfv then
minetest.after(3, function()
minetest.chat_send_player(name, msgiplevelone);
if minetest.settings:get_bool("beowulf.dfdetect.enable_kick", false) then
minetest.kick_player(name, "Are you a cheater stupid user? change your cracked client for play")
end
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgm); -- advertise moderators
end
end)
minetest.log(msgm)
end
else
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgm); -- advertise moderators
end
minetest.log(msgm)
end
end)
minetest.register_chatcommand("cchk", {
privs = {
interact = true
},
description = "cchk NAME, checks if player is cheating in this moment",
func = function(name, param)
local privs = minetest.get_player_privs(name).privs;
if not cheat.moderators[name] and not privs then return end
local player = minetest.get_player_by_name(param);
if not player then return end
check_player(player);
local ip = tostring(minetest.get_player_ip(param));
local info = minetest.get_player_information(param)
local msgm = "#anticheat detected a cheater with "..ip.." named "..param
if info.version_string then
local dfv = dfver(info.version_string)
if dfv then
msgm = msgm.." using "..info.version_string
minetest.chat_send_player(param, "..... cheat");
if minetest.settings:get_bool("beowulf.dfdetect.enable_kick", false) then
minetest.kick_player(param, "Are you a cheater stupid user? change your cracked client for play")
end
minetest.chat_send_player(name,msgm); -- advertise moderators
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgm); -- advertise moderators
end
minetest.log(msgm)
end
else
for namemd,_ in pairs(cheat.moderators) do
minetest.chat_send_player(namemd,msgm); -- advertise moderators
end
minetest.log(msgm)
end
local players = minetest.get_connected_players();
for name,_ in pairs(cheat.debuglist) do -- show suspects in debug
for _,player in pairs(players) do
local pname = player:get_player_name();
if cheat.players[pname].count>0 then
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].clearpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
end
end
end
end
})
minetest.register_chatcommand("crep", { -- see cheat report
privs = {
interact = true
},
description = "crep 0/1, 0 = default cheat report, 1 = connected player stats",
func = function(name, param)
local privs = minetest.get_player_privs(name).privs;
if not cheat.moderators[name] and not privs then return end
if param == "" then
minetest.chat_send_player(name,"use: crep type, types: 0(default) cheat report, 1 connected player stats (".. version ..")");
end
param = tonumber(param) or 0;
if param == 0 then -- show cheat report
local text = "";
for ip,v in pairs(anticheatdb) do
if v and v.name and v.msg then
text = text .. "ip " .. ip .. " ".. v.msg .. "\n";
end
end
if text ~= "" then
local form = "size [6,7] textarea[0,0;6.5,8.5;creport;CHEAT REPORT;".. text.."]"
minetest.show_formspec(name, "anticheatreport", form)
end
elseif param == 1 then -- show cheat stats
local text = "";
local players = minetest.get_connected_players();
for _,player in pairs(players) do
local pname = player:get_player_name();
local ip = tostring(minetest.get_player_ip(pname));
text = text .. "\nname " .. pname .. ", digxp " .. math.floor(1000*cheat.players[pname].stats.digxp)/1000 ..
", deltadigxp(2min) " .. math.floor(1000*cheat.players[pname].stats.deltadig)/1000 .. ", maxdeltadigxp " .. math.floor(1000*cheat.players[pname].stats.maxdeltadig)/1000; -- .. " ".. string.gsub(dump(cheat.players[pname].stats), "\n", " ");
if anticheatdb[ip] then text = text .. " (DETECTED) ip ".. ip .. ", name " .. anticheatdb[ip].name end
end
if text ~= "" then
local form = "size [10,8] textarea[0,0;10.5,9.;creport;CHEAT STATISTICS;".. text.."]"
minetest.show_formspec(name, "anticheatreport", form)
end
end
-- suspects info
local players = minetest.get_connected_players();
for _,player in pairs(players) do
local pname = player:get_player_name();
if cheat.players[pname].count>0 then
minetest.chat_send_player(name, "name " .. pname .. ", cheat pos " .. minetest.pos_to_string(cheat.players[pname].cheatpos) .. " last clear pos " .. minetest.pos_to_string(cheat.players[pname].lastpos) .. " cheat type " .. cheat.players[pname].cheattype .. " cheatcount " .. cheat.players[pname].count );
end
end
end
})
minetest.register_chatcommand("cdebug", { -- toggle cdebug= display of stats on/off for this player
privs = {
interact = true
},
func = function(name, param)
local privs = minetest.get_player_privs(name).privs;
if not cheat.moderators[name] and not privs then return end
if cheat.debuglist[name] == nil then cheat.debuglist[name] = true else cheat.debuglist[name] = nil end;
minetest.chat_send_player(name,"#anticheat: " .. version);
if cheat.debuglist[name]==true then
minetest.chat_send_player(name,"#anticheat: display of debug messages is ON");
else
minetest.chat_send_player(name,"#anticheat: display of debug messages is OFF");
end
end
})
-------------------------------------------------------------------------
-- GOVERNONR custom action for features mod code improvements
-- minetest routines to implement the mod features
-- Copyright 2020-2023 mckaygerhard CC-BY-SA-NC 4.0
-------------------------------------------------------------------------
-- cleanup after the player leaves
minetest.register_on_leaveplayer(function(player)
if is_player(player) then
track_player_clear(player)
end
end)
-- list of nodes to enable damage if noclip or to check if player its diggin too fast
local node_list_check = {}
-- TODO move this to a comma separted list in config, and adde the check below in node check for fly
if minetest.get_modpath("default") then
table.insert(node_list_check, "default:stone")
table.insert(node_list_check, "default:stone_with_iron")
table.insert(node_list_check, "default:stone_with_copper")
table.insert(node_list_check, "default:stone_with_coal")
table.insert(node_list_check, "default:stone_with_gold")
table.insert(node_list_check, "default:stone_with_mese")
table.insert(node_list_check, "default:stone_with_diamond")
end
local function interval_fn()
if not governing.modbeowulf then
for _, player in ipairs(minetest.get_connected_players()) do
track_player(player)
end
end
if not governing.modanticheat then
for _, nodename in ipairs(node_list_check) do
set_check_can_dig(nodename);
end
end
minetest.after(2, interval_fn)
end
-- TODO: beowulf was pretty unneficient on large player servers.. cos only its made each 1 second and again each x inside function
if governing.is_50 then
minetest.register_on_mods_loaded(interval_fn )
else
minetest.after(0.1, interval_fn)
end
-- damaged if a player its making noclip, no matter if admin is doing
for _, nodename in ipairs(node_list_check) do
minetest.override_item(nodename, {
damage_per_second = 1
})
end