Server-side checking of digging; disable_anticheat setting

This commit is contained in:
Perttu Ahola 2012-07-21 14:38:49 +03:00
parent b0ba05c9ac
commit 2795f44f03
6 changed files with 101 additions and 7 deletions

@ -158,6 +158,8 @@
#static_spawnpoint = 0, 10, 0 #static_spawnpoint = 0, 10, 0
# If true, new players cannot join with an empty password # If true, new players cannot join with an empty password
#disallow_empty_password = false #disallow_empty_password = false
# If true, disable cheat prevention in multiplayer
#disable_anticheat = false
# Profiler data print interval. #0 = disable. # Profiler data print interval. #0 = disable.
#profiler_print_interval = 0 #profiler_print_interval = 0

@ -764,6 +764,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
m_last_good_position(0,0,0), m_last_good_position(0,0,0),
m_last_good_position_age(0), m_last_good_position_age(0),
m_time_from_last_punch(0), m_time_from_last_punch(0),
m_nocheat_dig_pos(32767, 32767, 32767),
m_nocheat_dig_time(0),
m_wield_index(0), m_wield_index(0),
m_position_not_sent(false), m_position_not_sent(false),
m_armor_groups_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_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 = m_player->getPosition();
m_last_good_position_age = 0; 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 NOTE: Actually the server should handle player physics like the
client does and compare player's position to what is calculated client does and compare player's position to what is calculated
on our side. This is required when eg. players fly due to an 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; float player_max_speed = 0;

@ -173,6 +173,9 @@ public:
{ {
return m_peer_id; return m_peer_id;
} }
// Cheat prevention
v3f getLastGoodPosition() const v3f getLastGoodPosition() const
{ {
return m_last_good_position; return m_last_good_position;
@ -183,6 +186,26 @@ public:
m_time_from_last_punch = 0.0; m_time_from_last_punch = 0.0;
return r; 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, void updatePrivileges(const std::set<std::string> &privs,
bool is_singleplayer) bool is_singleplayer)
{ {
@ -196,9 +219,14 @@ private:
Player *m_player; Player *m_player;
u16 m_peer_id; u16 m_peer_id;
Inventory *m_inventory; Inventory *m_inventory;
// Cheat prevention
v3f m_last_good_position; v3f m_last_good_position;
float m_last_good_position_age; float m_last_good_position_age;
float m_time_from_last_punch; float m_time_from_last_punch;
v3s16 m_nocheat_dig_pos;
float m_nocheat_dig_time;
int m_wield_index; int m_wield_index;
bool m_position_not_sent; bool m_position_not_sent;
ItemGroupList m_armor_groups; ItemGroupList m_armor_groups;

@ -121,6 +121,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("unlimited_player_transfer_distance", "true"); settings->setDefault("unlimited_player_transfer_distance", "true");
settings->setDefault("enable_pvp", "true"); settings->setDefault("enable_pvp", "true");
settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false");
settings->setDefault("profiler_print_interval", "0"); settings->setDefault("profiler_print_interval", "0");
settings->setDefault("enable_mapgen_debug_info", "false"); settings->setDefault("enable_mapgen_debug_info", "false");

@ -2113,6 +2113,8 @@ void the_game(
} }
MapNode n = client.getEnv().getClientMap().getNode(nodepos); MapNode n = client.getEnv().getClientMap().getNode(nodepos);
// NOTE: Similar piece of code exists on the server side for
// cheat detection.
// Get digging parameters // Get digging parameters
DigParams params = getDigParams(nodedef->get(n).groups, DigParams params = getDigParams(nodedef->get(n).groups,
&playeritem_toolcap); &playeritem_toolcap);

@ -3014,6 +3014,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
} }
if(n.getContent() != CONTENT_IGNORE) if(n.getContent() != CONTENT_IGNORE)
scriptapi_node_on_punch(m_lua, p_under, n, playersao); scriptapi_node_on_punch(m_lua, p_under, n, playersao);
// Cheat prevention
playersao->noCheatDigStart(p_under);
} }
else if(pointed.type == POINTEDTHING_OBJECT) else if(pointed.type == POINTEDTHING_OBJECT)
{ {
@ -3051,7 +3053,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
*/ */
else if(action == 2) else if(action == 2)
{ {
// Only complete digging of nodes // Only digging of nodes
if(pointed.type == POINTEDTHING_NODE) if(pointed.type == POINTEDTHING_NODE)
{ {
MapNode n(CONTENT_IGNORE); MapNode n(CONTENT_IGNORE);
@ -3067,9 +3069,64 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
m_emerge_queue.addBlock(peer_id, m_emerge_queue.addBlock(peer_id,
getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK); 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); scriptapi_node_on_dig(m_lua, p_under, n, playersao);
// Send unusual result (that is, node not being removed)
if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR) if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
{ {
// Re-send block to revert change on client-side // Re-send block to revert change on client-side