init.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. -- Anticheat module.
  2. -- This file is reloadable.
  3. if not minetest.global_exists("gdac") then gdac = {} end
  4. gdac.session_violations = gdac.session_violations or {}
  5. gdac.modpath = minetest.get_modpath("gdac")
  6. -- Localize for performance.
  7. local vector_distance = vector.distance
  8. local vector_round = vector.round
  9. local math_random = math.random
  10. dofile(gdac.modpath .. "/position_logger.lua")
  11. dofile(gdac.modpath .. "/anti_servspam.lua")
  12. dofile(gdac.modpath .. "/autokick.lua")
  13. function gdac.player_is_admin(playerorname)
  14. if minetest.is_singleplayer() then
  15. return false
  16. end
  17. --do return false end
  18. local pref = playerorname
  19. if type(pref) == "string" then
  20. pref = minetest.get_player_by_name(pref)
  21. end
  22. if pref then
  23. return minetest.check_player_privs(pref, {server=true})
  24. end
  25. end
  26. -- Per-player data.
  27. gdac.players = gdac.players or {}
  28. -- Settings. These can only be changed before startup.
  29. -- Note: all disabled due to performance issues! Revealed by profiling. Do NOT enable.
  30. gdac.cheats_logfile = "cheats.txt"
  31. gdac.detect_mining_hacks = false
  32. gdac.block_mining_hacks = false
  33. gdac.detect_long_range_interact = false -- This has too many false positives.
  34. gdac.detect_fly = false
  35. gdac.detect_clip = false
  36. -- Settings. These can be changed after startup.
  37. gdac.name_of_admin = "MustTest"
  38. gdac.interact_range_limit = 6.5
  39. gdac.fly_timeout_min = 8
  40. gdac.fly_timeout_max = 32
  41. gdac.clip_timeout_min = 8
  42. gdac.clip_timeout_max = 32
  43. -- Logging function.
  44. gdac.log = function(message)
  45. if gdac.logfile then
  46. gdac.logfile:write(message .. "\r\n")
  47. gdac.logfile:flush()
  48. end
  49. local admin = minetest.get_player_by_name(gdac.name_of_admin)
  50. if admin and admin:is_player() then
  51. minetest.chat_send_player(gdac.name_of_admin, "# Server: " .. message)
  52. end
  53. end
  54. gdac.add_session_violation = function(name)
  55. -- "Table index is nil" means nil name was passed to this function.
  56. if gdac.session_violations[name] == nil then gdac.session_violations[name] = 0 end
  57. gdac.session_violations[name] = gdac.session_violations[name] + 1
  58. end
  59. local floor = math.floor
  60. local round = function(num)
  61. local digits = 1
  62. local shift = 10 ^ digits
  63. return floor(num * shift + 0.5 ) / shift
  64. end
  65. -- This function is responsible for checking if the digger is hacking.
  66. gdac.check_mined_invisible = function(pos, nodename, digger)
  67. -- What do we do if a non-player dug something? Probably a bug elsewhere in the code!
  68. if not digger or not digger:is_player() then return false end
  69. local pt = {x=pos.x, y=pos.y+1, z=pos.z}
  70. local pb = {x=pos.x, y=pos.y-1, z=pos.z}
  71. local p1 = {x=pos.x+1, y=pos.y, z=pos.z}
  72. local p2 = {x=pos.x-1, y=pos.y, z=pos.z}
  73. local p3 = {x=pos.x, y=pos.y, z=pos.z+1}
  74. local p4 = {x=pos.x, y=pos.y, z=pos.z-1}
  75. if minetest.get_node(pt).name == 'air' or
  76. minetest.get_node(pb).name == 'air' or
  77. minetest.get_node(p1).name == 'air' or
  78. minetest.get_node(p2).name == 'air' or
  79. minetest.get_node(p3).name == 'air' or
  80. minetest.get_node(p4).name == 'air' then
  81. return true -- The block was visible, so mining it was legal.
  82. else
  83. local nodes = {
  84. minetest.get_node(pt).name,
  85. minetest.get_node(pb).name,
  86. minetest.get_node(p1).name,
  87. minetest.get_node(p2).name,
  88. minetest.get_node(p3).name,
  89. minetest.get_node(p4).name,
  90. }
  91. -- Block dug is surrounded on all sides by non-air nodes. But check if any of
  92. -- these nodes are actually not full blocks.
  93. for k, v in pairs(nodes) do
  94. local vt = minetest.reg_ns_nodes[v]
  95. if not vt then
  96. -- Either a stairs node, or unknown/ignore.
  97. return true
  98. end
  99. if vt then
  100. if not vt.walkable or vt.climbable then
  101. return true -- Could be ladder, torch, etc.
  102. end
  103. if vt.drawtype and vt.drawtype ~= "normal" then
  104. return true -- Probably not a full block.
  105. end
  106. end
  107. end
  108. -- LOL wut? Mining a block that can't possibly be seen!
  109. local pname = digger:get_player_name()
  110. gdac.add_session_violation(pname)
  111. gdac.log("Almost certainly a cheater: <" .. pname ..
  112. "> dug '" .. nodename .. "' at (" .. pos.x .. "," .. pos.y .. "," .. pos.z ..
  113. "), which was NOT EXPOSED. SV: " ..
  114. gdac.session_violations[pname] .. ".")
  115. return false -- Can't dig.
  116. end
  117. end
  118. local check_fly = function(pos)
  119. if minetest.get_node(vector.add(pos, {x=0, y=-1, z=0})).name ~= "air" then
  120. return false
  121. end
  122. --local p = vector_round(pos)
  123. -- Check up to 2 meters below player, and 1 meter all around.
  124. -- Fly cheaters tend to be pretty blatent in their cheating,
  125. -- and I want to avoid logging players who do a lot of jumping.
  126. local minp = {x=pos.x-1, y=pos.y-2, z=pos.z-1}
  127. local maxp = {x=pos.x+1, y=pos.y+0, z=pos.z+1}
  128. local tb = minetest.find_nodes_in_area(minp, maxp, "air")
  129. if #tb >= 27 then
  130. -- If all nodes under player are air, then player is not supported.
  131. return true
  132. end
  133. return false
  134. end
  135. local check_fly_again
  136. check_fly_again = function(name, old_pos)
  137. local player = minetest.get_player_by_name(name)
  138. if player and player:is_player() then
  139. if player:get_hp() <= 0 then return end -- Player is dead!
  140. local new_pos = player:get_pos()
  141. local still_cheating = check_fly(new_pos)
  142. if still_cheating == true then
  143. local y1 = new_pos.y
  144. local y2 = old_pos.y
  145. local d = y2 - y1
  146. if d < 0.1 then -- If distance is negative or *close to it* then player probably is not falling.
  147. -- If player hasn't moved they may have just glitched accidentally.
  148. if vector_distance(new_pos, old_pos) > 0.5 then
  149. gdac.add_session_violation(name)
  150. gdac.log("Possible flier? <" .. name ..
  151. "> caught flying at " .. minetest.pos_to_string(vector_round(new_pos)) .. ". SV: " ..
  152. gdac.session_violations[name] .. ".")
  153. end
  154. -- Still cheating? Check again. This will cause log spam if player cheats continuously, so will be more visible.
  155. minetest.after(1, check_fly_again, name, new_pos)
  156. end
  157. end
  158. end
  159. end
  160. gdac.antifly_globalstep = function(dtime)
  161. local players = minetest.get_connected_players()
  162. for k, v in ipairs(players) do
  163. if not minetest.check_player_privs(v, {fly=true}) and
  164. v:get_hp() > 0 then -- Dead players tend to trigger this.
  165. local name = v:get_player_name()
  166. local data = gdac.players[name]
  167. assert(data ~= nil)
  168. local check = false -- Do we need to check?
  169. local cheat = false -- Have we detected a possible cheat?
  170. -- Check timer and timeout.
  171. local flytimer = data.flytimer
  172. local flytimeout = data.flytimeout
  173. flytimer = flytimer + dtime
  174. if flytimer > flytimeout then
  175. flytimer = 0
  176. -- Random time to next check so that it cannot be predicted.
  177. flytimeout = math_random(gdac.fly_timeout_min, gdac.fly_timeout_max)
  178. check = true
  179. end
  180. data.flytimer = flytimer
  181. data.flytimeout = flytimeout
  182. -- Check for flying.
  183. if check == true then
  184. cheat = check_fly(v:get_pos())
  185. end
  186. if cheat == true then
  187. -- If cheat detected, check again after a short delay in order to confirm.
  188. minetest.after(1, check_fly_again, name, v:get_pos())
  189. end
  190. end -- If player does not have fly priv.
  191. end
  192. end
  193. local check_drawtype = function(drawtype)
  194. if drawtype == "normal" then
  195. return true
  196. elseif drawtype == "glasslike" then
  197. return true
  198. elseif drawtype == "glasslike_framed" then
  199. return true
  200. elseif drawtype == "glasslike_framed_optional" then
  201. return true
  202. elseif drawtype == "allfaces" then
  203. return true
  204. elseif drawtype == "allfaces_optional" then
  205. return true
  206. end
  207. end
  208. local check_clip = function(pos)
  209. local p = vector_round(pos)
  210. local p1 = {x=p.x, y=p.y, z=p.z}
  211. local p2 = {x=p.x, y=p.y+1, z=p.z}
  212. local n1 = minetest.get_node(p1).name
  213. local n2 = minetest.get_node(p2).name
  214. if n1 ~= "air" and n2 ~= "air" then
  215. local d1 = minetest.registered_nodes[n1]
  216. local d2 = minetest.registered_nodes[n2]
  217. local b1 = (d1.walkable == true and check_drawtype(d1.drawtype))
  218. local b2 = (d2.walkable == true and check_drawtype(d2.drawtype))
  219. if b1 and b2 then
  220. return true
  221. end
  222. end
  223. return false
  224. end
  225. local check_clip_again
  226. check_clip_again = function(name, old_pos)
  227. local player = minetest.get_player_by_name(name)
  228. if player and player:is_player() then
  229. if player:get_hp() <= 0 then return end -- Player is dead!
  230. local new_pos = player:get_pos()
  231. local still_cheating = check_clip(new_pos)
  232. if still_cheating == true then
  233. -- If player hasn't moved they may have just glitched accidentally.
  234. if vector_distance(new_pos, old_pos) > 0.5 then
  235. gdac.add_session_violation(name)
  236. gdac.log("Possible noclipper? <" .. name ..
  237. "> caught inside \"" .. minetest.get_node(new_pos).name .. "\" at " .. minetest.pos_to_string(vector_round(new_pos)) .. ". SV: " ..
  238. gdac.session_violations[name] .. ".")
  239. end
  240. -- Still cheating? Check again. This will cause log spam if player cheats continuously, so will be more visible.
  241. minetest.after(1, check_clip_again, name, new_pos)
  242. end
  243. end
  244. end
  245. gdac.anticlip_globalstep = function(dtime)
  246. local players = minetest.get_connected_players()
  247. for k, v in ipairs(players) do
  248. if not minetest.check_player_privs(v, {noclip=true}) and
  249. v:get_hp() > 0 then -- Dead players tend to trigger this.
  250. local name = v:get_player_name()
  251. local data = gdac.players[name]
  252. assert(data ~= nil)
  253. local check = false -- Do we need to check?
  254. local cheat = false -- Have we detected a possible cheat?
  255. -- Check timer and timeout.
  256. local cliptimer = data.cliptimer
  257. local cliptimeout = data.cliptimeout
  258. cliptimer = cliptimer + dtime
  259. if cliptimer > cliptimeout then
  260. cliptimer = 0
  261. -- Random time to next check so that it cannot be predicted.
  262. cliptimeout = math_random(gdac.clip_timeout_min, gdac.clip_timeout_max)
  263. check = true
  264. end
  265. data.cliptimer = cliptimer
  266. data.cliptimeout = cliptimeout
  267. -- Check for noclipping.
  268. if check == true then
  269. cheat = check_clip(v:get_pos())
  270. end
  271. if cheat == true then
  272. -- If cheat detected, check again after a short delay in order to confirm.
  273. minetest.after(1, check_clip_again, name, v:get_pos())
  274. end
  275. end -- If player does not have noclip priv.
  276. end
  277. end
  278. gdac.check_long_range_interact = function(pos, node, digger, strpart)
  279. local ppos = digger:get_pos()
  280. local d = vector_distance(pos, ppos)
  281. if d > gdac.interact_range_limit then
  282. local pname = digger:get_player_name()
  283. local nodename = node.name
  284. gdac.add_session_violation(pname)
  285. gdac.log("Possible cheater? <" .. pname ..
  286. "> " .. strpart .. " '" .. nodename .. "' at " .. minetest.pos_to_string(vector_round(pos)) ..
  287. "; TOO FAR from player at " .. minetest.pos_to_string(vector_round(ppos)) ..
  288. ". D: " .. round(d) .. ". SV: " ..
  289. gdac.session_violations[pname] .. ".")
  290. end
  291. end
  292. if not gdac.registered then
  293. -- Install logging file with shutdown handler.
  294. do
  295. local path = minetest.get_worldpath() .. "/" .. gdac.cheats_logfile
  296. gdac.logfile = io.open(path, "a+")
  297. minetest.register_on_shutdown(function()
  298. if gdac.logfile then
  299. gdac.logfile:flush()
  300. gdac.logfile:close()
  301. end
  302. end)
  303. end
  304. if gdac.detect_mining_hacks then
  305. -- Helper function to reduce code size.
  306. local register_mining_hack = function(nodename)
  307. if gdac.block_mining_hacks then
  308. -- Log and prevent hack.
  309. local def = minetest.registered_items[nodename]
  310. local old_can_dig = def.can_dig
  311. minetest.override_item(nodename, {
  312. can_dig = function(pos, digger)
  313. if old_can_dig then
  314. old_can_dig(pos, digger)
  315. end
  316. return gdac.check_mined_invisible(pos, nodename, digger)
  317. end,
  318. })
  319. else
  320. -- Log only.
  321. local def = minetest.registered_items[nodename]
  322. local old_after_dig_node = def.after_dig_node
  323. minetest.override_item(nodename, {
  324. after_dig_node = function(pos, oldnode, oldmeta, digger)
  325. if old_after_dig_node then
  326. old_after_dig_node(pos, oldnode, oldmeta, digger)
  327. end
  328. return gdac.check_mined_invisible(pos, nodename, digger)
  329. end,
  330. })
  331. end
  332. end
  333. if minetest.get_modpath("default") then
  334. register_mining_hack("default:stone_with_coal")
  335. register_mining_hack("default:stone_with_mese")
  336. register_mining_hack("default:stone_with_iron")
  337. register_mining_hack("default:stone_with_copper")
  338. register_mining_hack("default:stone_with_gold")
  339. register_mining_hack("default:mese")
  340. end
  341. if minetest.get_modpath("moreores") then
  342. register_mining_hack("moreores:mineral_mithril")
  343. register_mining_hack("moreores:mineral_tin")
  344. register_mining_hack("moreores:mineral_silver")
  345. end
  346. if minetest.get_modpath("morerocks") then
  347. register_mining_hack("morerocks:marble")
  348. register_mining_hack("morerocks:granite")
  349. register_mining_hack("morerocks:marble_pink")
  350. register_mining_hack("morerocks:marble_white")
  351. register_mining_hack("morerocks:serpentine")
  352. end
  353. if minetest.get_modpath("quartz") then
  354. register_mining_hack("quartz:quartz_ore")
  355. end
  356. register_mining_hack("chromium:ore")
  357. register_mining_hack("zinc:ore")
  358. register_mining_hack("sulfur:ore")
  359. register_mining_hack("uranium:ore")
  360. register_mining_hack("kalite:ore")
  361. register_mining_hack("akalin:ore")
  362. register_mining_hack("alatro:ore")
  363. register_mining_hack("arol:ore")
  364. register_mining_hack("talinite:ore")
  365. end
  366. -- Detect digging at long range.
  367. local random = math_random
  368. minetest.register_on_dignode(function(pos, oldnode, digger)
  369. if not digger or not digger:is_player() then return end
  370. -- Disabled for performance reasons.
  371. --if gdac.detect_long_range_interact then
  372. -- gdac.check_long_range_interact(pos, oldnode, digger, "dug")
  373. --end
  374. -- Check advanced falling node logic.
  375. instability.check_unsupported_around(pos)
  376. -- Only *sometimes* create dig particles for other players.
  377. if random(1, 5) == 1 then
  378. ambiance.particles_on_dig(pos, oldnode)
  379. end
  380. end)
  381. local function node_not_walkable(pos)
  382. local nn = minetest.get_node(pos).name
  383. if nn == "air" then return true end
  384. local def = minetest.registered_nodes[nn]
  385. if def and not def.walkable then return true end
  386. end
  387. minetest.register_on_placenode(function(pos, newnode, digger)
  388. if not digger or not digger:is_player() then return end
  389. -- Detect node placement at long range.
  390. --if gdac.detect_long_range_interact then
  391. -- gdac.check_long_range_interact(pos, newnode, digger, "placed")
  392. --end
  393. local dropped = false
  394. local control = digger:get_player_control()
  395. if control.aux1 and node_not_walkable({x=pos.x, y=pos.y-1, z=pos.z}) then
  396. local ndef = minetest.registered_nodes[newnode.name]
  397. local groups = ndef.groups or {}
  398. -- Player may not drop wallmounted nodes, attached nodes, or hanging nodes.
  399. if ndef.paramtype2 ~= "wallmounted" and (groups.attached_node or 0) == 0 and (groups.hanging_node or 0) == 0 then
  400. if sfn.drop_node(pos) then
  401. dropped = true
  402. end
  403. end
  404. end
  405. if not dropped then
  406. instability.check_tower(pos, newnode, digger)
  407. instability.check_single_node(pos)
  408. -- Check advanced falling node logic.
  409. instability.check_unsupported_single(pos)
  410. end
  411. if random(1, 5) == 1 then
  412. ambiance.particles_on_place(pos, newnode)
  413. end
  414. end)
  415. -- Register antifly routines.
  416. --if gdac.detect_fly then
  417. -- minetest.register_globalstep(function(...) return gdac.antifly_globalstep(...) end)
  418. --end
  419. -- Register anticlip routines.
  420. --if gdac.detect_clip then
  421. -- minetest.register_globalstep(function(...) return gdac.anticlip_globalstep(...) end)
  422. --end
  423. -- Set up information for new players.
  424. --minetest.register_on_joinplayer(function(player)
  425. -- gdac.players[player:get_player_name()] = {
  426. -- -- Fly data.
  427. -- flytimer = 0,
  428. -- flytimeout = math_random(gdac.fly_timeout_min, gdac.fly_timeout_max),
  429. --
  430. -- -- Noclip data.
  431. -- cliptimer = 0,
  432. -- cliptimeout = math_random(gdac.clip_timeout_min, gdac.clip_timeout_max),
  433. -- }
  434. --end)
  435. -- Admin starts invisible on join, can drop cloak later.
  436. minetest.register_on_joinplayer(function(player)
  437. if gdac.player_is_admin(player) then
  438. gdac_invis.toggle_invisibility(player:get_player_name(), "")
  439. end
  440. end)
  441. -- Reloadable.
  442. local name = "gdac:core"
  443. local file = gdac.modpath .. "/init.lua"
  444. reload.register_file(name, file, false)
  445. gdac.registered = true
  446. end