init.lua 15 KB

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