Server-side checking of digging; disable_anticheat setting
authorPerttu Ahola <celeron55@gmail.com>
Sat, 21 Jul 2012 11:38:49 +0000 (14:38 +0300)
committerPerttu Ahola <celeron55@gmail.com>
Sat, 21 Jul 2012 11:38:49 +0000 (14:38 +0300)
minetest.conf.example
src/content_sao.cpp
src/content_sao.h
src/defaultsettings.cpp
src/game.cpp
src/server.cpp

index ef1b8d2615bf4c66cd98038b208d5f6f3db99f19..fdfbf201cd39b562d5dcb44eedfee66a3be0daa1 100644 (file)
 #static_spawnpoint = 0, 10, 0
 # If true, new players cannot join with an empty password
 #disallow_empty_password = false
+# If true, disable cheat prevention in multiplayer
+#disable_anticheat = false
 
 # Profiler data print interval. #0 = disable.
 #profiler_print_interval = 0
index e9b5782a9b53b93dfde890e53eb63ea00b43ee53..0488c802394b536802370d6bfb3ff458a0553120 100644 (file)
@@ -764,6 +764,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
        m_last_good_position(0,0,0),
        m_last_good_position_age(0),
        m_time_from_last_punch(0),
+       m_nocheat_dig_pos(32767, 32767, 32767),
+       m_nocheat_dig_time(0),
        m_wield_index(0),
        m_position_not_sent(false),
        m_armor_groups_sent(false),
@@ -874,8 +876,9 @@ void PlayerSAO::step(float dtime, bool send_recommended)
        }
 
        m_time_from_last_punch += dtime;
+       m_nocheat_dig_time += dtime;
        
-       if(m_is_singleplayer)
+       if(m_is_singleplayer || g_settings->getBool("disable_anticheat"))
        {
                m_last_good_position = m_player->getPosition();
                m_last_good_position_age = 0;
@@ -888,7 +891,8 @@ void PlayerSAO::step(float dtime, bool send_recommended)
                        NOTE: Actually the server should handle player physics like the
                        client does and compare player's position to what is calculated
                        on our side. This is required when eg. players fly due to an
-                       explosion.
+                       explosion. Altough a node-based alternative might be possible
+                       too, and much more lightweight.
                */
 
                float player_max_speed = 0;
index 6efb9e3ec84d422518b8ed4d7e40802acd3fa753..fac16ca994c5af6da1dd3d182ce426256d5196e6 100644 (file)
@@ -173,6 +173,9 @@ public:
        {
                return m_peer_id;
        }
+
+       // Cheat prevention
+
        v3f getLastGoodPosition() const
        {
                return m_last_good_position;
@@ -183,6 +186,26 @@ public:
                m_time_from_last_punch = 0.0;
                return r;
        }
+       void noCheatDigStart(v3s16 p)
+       {
+               m_nocheat_dig_pos = p;
+               m_nocheat_dig_time = 0;
+       }
+       v3s16 getNoCheatDigPos()
+       {
+               return m_nocheat_dig_pos;
+       }
+       float getNoCheatDigTime()
+       {
+               return m_nocheat_dig_time;
+       }
+       void noCheatDigEnd()
+       {
+               m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
+       }
+
+       // Other
+
        void updatePrivileges(const std::set<std::string> &privs,
                        bool is_singleplayer)
        {
@@ -196,9 +219,14 @@ private:
        Player *m_player;
        u16 m_peer_id;
        Inventory *m_inventory;
+
+       // Cheat prevention
        v3f m_last_good_position;
        float m_last_good_position_age;
        float m_time_from_last_punch;
+       v3s16 m_nocheat_dig_pos;
+       float m_nocheat_dig_time;
+
        int m_wield_index;
        bool m_position_not_sent;
        ItemGroupList m_armor_groups;
index a9c0de6a7d9bd7c6401db4c61520f35b7fe4095b..dabdbb4aa5f46bc0b86011b9f05e58764876b14d 100644 (file)
@@ -121,6 +121,7 @@ void set_default_settings(Settings *settings)
        settings->setDefault("unlimited_player_transfer_distance", "true");
        settings->setDefault("enable_pvp", "true");
        settings->setDefault("disallow_empty_password", "false");
+       settings->setDefault("disable_anticheat", "false");
 
        settings->setDefault("profiler_print_interval", "0");
        settings->setDefault("enable_mapgen_debug_info", "false");
index b29d2d646693db0e3eed9d2cd20cd9ae029f64a3..e2e6dd8c1774df7eec004603ae68b5a6e18feab7 100644 (file)
@@ -2112,7 +2112,9 @@ void the_game(
                                        ldown_for_dig = true;
                                }
                                MapNode n = client.getEnv().getClientMap().getNode(nodepos);
-
+                               
+                               // NOTE: Similar piece of code exists on the server side for
+                               // cheat detection.
                                // Get digging parameters
                                DigParams params = getDigParams(nodedef->get(n).groups,
                                                &playeritem_toolcap);
@@ -2160,7 +2162,7 @@ void the_game(
                                {
                                        dig_index = crack_animation_length;
                                }
-                               
+
                                // Don't show cracks if not diggable
                                if(dig_time_complete >= 100000.0)
                                {
index 80df9fc7ada4958d445f09511672f119d3dae1db..a44a6a2eedf6c62d8034f0b2834f7632184d77b5 100644 (file)
@@ -3014,6 +3014,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                }
                                if(n.getContent() != CONTENT_IGNORE)
                                        scriptapi_node_on_punch(m_lua, p_under, n, playersao);
+                               // Cheat prevention
+                               playersao->noCheatDigStart(p_under);
                        }
                        else if(pointed.type == POINTEDTHING_OBJECT)
                        {
@@ -3051,7 +3053,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                */
                else if(action == 2)
                {
-                       // Only complete digging of nodes
+                       // Only digging of nodes
                        if(pointed.type == POINTEDTHING_NODE)
                        {
                                MapNode n(CONTENT_IGNORE);
@@ -3067,10 +3069,65 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
                                        m_emerge_queue.addBlock(peer_id,
                                                        getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK);
                                }
-                               if(n.getContent() != CONTENT_IGNORE)
+
+                               /* Cheat prevention */
+                               bool is_valid_dig = true;
+                               if(!isSingleplayer() && !g_settings->getBool("disable_anticheat"))
+                               {
+                                       v3s16 nocheat_p = playersao->getNoCheatDigPos();
+                                       float nocheat_t = playersao->getNoCheatDigTime();
+                                       playersao->noCheatDigEnd();
+                                       // If player didn't start digging this, ignore dig
+                                       if(nocheat_p != p_under){
+                                               infostream<<"Server: NoCheat: "<<player->getName()
+                                                               <<" started digging "
+                                                               <<PP(nocheat_p)<<" and completed digging "
+                                                               <<PP(p_under)<<"; not digging."<<std::endl;
+                                               is_valid_dig = false;
+                                       }
+                                       // Get player's wielded item
+                                       ItemStack playeritem;
+                                       InventoryList *mlist = playersao->getInventory()->getList("main");
+                                       if(mlist != NULL)
+                                               playeritem = mlist->getItem(playersao->getWieldIndex());
+                                       ToolCapabilities playeritem_toolcap =
+                                                       playeritem.getToolCapabilities(m_itemdef);
+                                       // Get diggability and expected digging time
+                                       DigParams params = getDigParams(m_nodedef->get(n).groups,
+                                                       &playeritem_toolcap);
+                                       // If can't dig, try hand
+                                       if(!params.diggable){
+                                               const ItemDefinition &hand = m_itemdef->get("");
+                                               const ToolCapabilities *tp = hand.tool_capabilities;
+                                               if(tp)
+                                                       params = getDigParams(m_nodedef->get(n).groups, tp);
+                                       }
+                                       // If can't dig, ignore dig
+                                       if(!params.diggable){
+                                               infostream<<"Server: NoCheat: "<<player->getName()
+                                                               <<" completed digging "<<PP(p_under)
+                                                               <<", which is not diggable with tool. not digging."
+                                                               <<std::endl;
+                                               is_valid_dig = false;
+                                       }
+                                       // If time is considerably too short, ignore dig
+                                       // Check time only for medium and slow timed digs
+                                       if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
+                                               infostream<<"Server: NoCheat: "<<player->getName()
+                                                               <<" completed digging "
+                                                               <<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
+                                                               <<params.time<<"s; not digging."<<std::endl;
+                                               is_valid_dig = false;
+                                       }
+                               }
+
+                               /* Actually dig node */
+
+                               if(is_valid_dig && n.getContent() != CONTENT_IGNORE)
                                        scriptapi_node_on_dig(m_lua, p_under, n, playersao);
 
-                               if (m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
+                               // Send unusual result (that is, node not being removed)
+                               if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
                                {
                                        // Re-send block to revert change on client-side
                                        RemoteClient *client = getClient(peer_id);