Network: Change hash function for non-company passwords
Increase size of per-connection salts, simplify management Cache per-connection hashes at server end Send hashes as binary rather than bothering to stringify them
This commit is contained in:
@@ -37,7 +37,9 @@
|
|||||||
#include "../error.h"
|
#include "../error.h"
|
||||||
#include "../core/checksum_func.hpp"
|
#include "../core/checksum_func.hpp"
|
||||||
#include "../string_func_extra.h"
|
#include "../string_func_extra.h"
|
||||||
|
#include "../core/serialisation.hpp"
|
||||||
#include "../3rdparty/randombytes/randombytes.h"
|
#include "../3rdparty/randombytes/randombytes.h"
|
||||||
|
#include "../3rdparty/monocypher/monocypher.h"
|
||||||
#include "../settings_internal.h"
|
#include "../settings_internal.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
@@ -221,6 +223,44 @@ std::string GenerateCompanyPasswordHash(const std::string &password, const std::
|
|||||||
return hashed_password.str();
|
return hashed_password.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HashBuffer : public BufferSerialisationHelper<HashBuffer> {
|
||||||
|
std::vector<byte> &buffer;
|
||||||
|
|
||||||
|
HashBuffer(std::vector<byte> &buffer) : buffer(buffer) {}
|
||||||
|
|
||||||
|
std::vector<byte> &GetSerialisationBuffer() { return this->buffer; }
|
||||||
|
size_t GetSerialisationLimit() const { return UINT32_MAX; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the given password using server ID and game seed.
|
||||||
|
* @param password Password to hash.
|
||||||
|
* @param password_server_id Server ID.
|
||||||
|
* @param password_game_seed Game seed.
|
||||||
|
* @return The hashed password.
|
||||||
|
*/
|
||||||
|
std::vector<uint8> GenerateGeneralPasswordHash(const std::string &password, const std::string &password_server_id, uint64 password_game_seed)
|
||||||
|
{
|
||||||
|
if (password.empty()) return {};
|
||||||
|
|
||||||
|
std::vector<byte> data;
|
||||||
|
data.reserve(password.size() + password_server_id.size() + 6);
|
||||||
|
HashBuffer buffer(data);
|
||||||
|
|
||||||
|
/* key field */
|
||||||
|
buffer.Send_uint64(password_game_seed);
|
||||||
|
|
||||||
|
/* message field */
|
||||||
|
buffer.Send_string(password_server_id);
|
||||||
|
buffer.Send_string(password);
|
||||||
|
|
||||||
|
std::vector<byte> output;
|
||||||
|
output.resize(64);
|
||||||
|
crypto_blake2b_general(output.data(), output.size(), data.data(), 8, data.data() + 8, data.size() - 8);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the company we want to join requires a password.
|
* Check if the company we want to join requires a password.
|
||||||
* @param company_id id of the company we want to check the 'passworded' flag for.
|
* @param company_id id of the company we want to check the 'passworded' flag for.
|
||||||
|
@@ -381,11 +381,11 @@ static uint32 last_ack_frame;
|
|||||||
/** One bit of 'entropy' used to generate a salt for the company passwords. */
|
/** One bit of 'entropy' used to generate a salt for the company passwords. */
|
||||||
static uint32 _company_password_game_seed;
|
static uint32 _company_password_game_seed;
|
||||||
/** One bit of 'entropy' used to generate a salt for the server passwords. */
|
/** One bit of 'entropy' used to generate a salt for the server passwords. */
|
||||||
static uint32 _server_password_game_seed;
|
static uint64 _server_password_game_seed;
|
||||||
/** One bit of 'entropy' used to generate a salt for the rcon passwords. */
|
/** One bit of 'entropy' used to generate a salt for the rcon passwords. */
|
||||||
static uint32 _rcon_password_game_seed;
|
static uint64 _rcon_password_game_seed;
|
||||||
/** One bit of 'entropy' used to generate a salt for the settings passwords. */
|
/** One bit of 'entropy' used to generate a salt for the settings passwords. */
|
||||||
static uint32 _settings_password_game_seed;
|
static uint64 _settings_password_game_seed;
|
||||||
/** The other bit of 'entropy' used to generate a salt for the server, rcon, and settings passwords. */
|
/** The other bit of 'entropy' used to generate a salt for the server, rcon, and settings passwords. */
|
||||||
static std::string _password_server_id;
|
static std::string _password_server_id;
|
||||||
/** The other bit of 'entropy' used to generate a salt for the company passwords. */
|
/** The other bit of 'entropy' used to generate a salt for the company passwords. */
|
||||||
@@ -439,7 +439,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
|
|||||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password)
|
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password)
|
||||||
{
|
{
|
||||||
Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD, SHRT_MAX);
|
Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD, SHRT_MAX);
|
||||||
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _server_password_game_seed));
|
p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _server_password_game_seed));
|
||||||
my_client->SendPacket(p);
|
my_client->SendPacket(p);
|
||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
}
|
}
|
||||||
@@ -464,9 +464,9 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSettingsPassword(const std
|
|||||||
{
|
{
|
||||||
Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD, SHRT_MAX);
|
Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD, SHRT_MAX);
|
||||||
if (password.empty()) {
|
if (password.empty()) {
|
||||||
p->Send_string("");
|
p->Send_buffer(nullptr, 0);
|
||||||
} else {
|
} else {
|
||||||
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _settings_password_game_seed));
|
p->Send_buffer(GenerateGeneralPasswordHash(password, _password_server_id, _settings_password_game_seed));
|
||||||
}
|
}
|
||||||
my_client->SendPacket(p);
|
my_client->SendPacket(p);
|
||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
@@ -638,7 +638,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit()
|
|||||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pass, const std::string &command)
|
NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pass, const std::string &command)
|
||||||
{
|
{
|
||||||
Packet *p = new Packet(PACKET_CLIENT_RCON, SHRT_MAX);
|
Packet *p = new Packet(PACKET_CLIENT_RCON, SHRT_MAX);
|
||||||
p->Send_string(GenerateCompanyPasswordHash(pass, _password_server_id, _rcon_password_game_seed));
|
p->Send_buffer(GenerateGeneralPasswordHash(pass, _password_server_id, _rcon_password_game_seed));
|
||||||
p->Send_string(command);
|
p->Send_string(command);
|
||||||
my_client->SendPacket(p);
|
my_client->SendPacket(p);
|
||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
@@ -836,7 +836,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSW
|
|||||||
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||||
this->status = STATUS_AUTH_GAME;
|
this->status = STATUS_AUTH_GAME;
|
||||||
|
|
||||||
_server_password_game_seed = p->Recv_uint32();
|
_server_password_game_seed = p->Recv_uint64();
|
||||||
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
||||||
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||||
|
|
||||||
@@ -876,9 +876,9 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet
|
|||||||
|
|
||||||
/* Initialize the password hash salting variables, even if they were previously. */
|
/* Initialize the password hash salting variables, even if they were previously. */
|
||||||
_company_password_game_seed = p->Recv_uint32();
|
_company_password_game_seed = p->Recv_uint32();
|
||||||
_server_password_game_seed = p->Recv_uint32();
|
_server_password_game_seed = p->Recv_uint64();
|
||||||
_rcon_password_game_seed = p->Recv_uint32();
|
_rcon_password_game_seed = p->Recv_uint64();
|
||||||
_settings_password_game_seed = p->Recv_uint32();
|
_settings_password_game_seed = p->Recv_uint64();
|
||||||
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
||||||
_company_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
_company_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
||||||
|
|
||||||
|
@@ -147,6 +147,7 @@ uint NetworkCalculateLag(const NetworkClientSocket *cs);
|
|||||||
StringID GetNetworkErrorMsg(NetworkErrorCode err);
|
StringID GetNetworkErrorMsg(NetworkErrorCode err);
|
||||||
bool NetworkMakeClientNameUnique(std::string &new_name);
|
bool NetworkMakeClientNameUnique(std::string &new_name);
|
||||||
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed);
|
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed);
|
||||||
|
std::vector<uint8> GenerateGeneralPasswordHash(const std::string &password, const std::string &password_server_id, uint64 password_game_seed);
|
||||||
std::string NetworkGenerateRandomKeyString(uint bytes);
|
std::string NetworkGenerateRandomKeyString(uint bytes);
|
||||||
|
|
||||||
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
|
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
#include "../core/random_func.hpp"
|
#include "../core/random_func.hpp"
|
||||||
#include "../rev.h"
|
#include "../rev.h"
|
||||||
#include "../crashlog.h"
|
#include "../crashlog.h"
|
||||||
|
#include "../3rdparty/randombytes/randombytes.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#if defined(__MINGW32__)
|
#if defined(__MINGW32__)
|
||||||
@@ -192,6 +193,16 @@ struct PacketWriter : SaveFilter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const std::vector<byte> &ServerNetworkGameSocketHandler::CachedPassword::GetHash(const std::string &password, uint64 password_game_seed)
|
||||||
|
{
|
||||||
|
if (password != this->source) {
|
||||||
|
this->source = password;
|
||||||
|
this->cached_hash = GenerateGeneralPasswordHash(password, _settings_client.network.network_id, password_game_seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->cached_hash;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new socket for the server side of the game connection.
|
* Create a new socket for the server side of the game connection.
|
||||||
* @param s The socket to connect with.
|
* @param s The socket to connect with.
|
||||||
@@ -201,9 +212,17 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo
|
|||||||
this->status = STATUS_INACTIVE;
|
this->status = STATUS_INACTIVE;
|
||||||
this->client_id = _network_client_id++;
|
this->client_id = _network_client_id++;
|
||||||
this->receive_limit = _settings_client.network.bytes_per_frame_burst;
|
this->receive_limit = _settings_client.network.bytes_per_frame_burst;
|
||||||
this->server_hash_bits = InteractiveRandom();
|
|
||||||
this->rcon_hash_bits = InteractiveRandom();
|
uint64 seeds[3];
|
||||||
this->settings_hash_bits = InteractiveRandom();
|
if (randombytes(&seeds, sizeof(uint64) * lengthof(seeds)) < 0) {
|
||||||
|
/* Can't get random data, use InteractiveRandom */
|
||||||
|
for (uint64 &seed : seeds) {
|
||||||
|
seed = (uint64)(InteractiveRandom()) | (((uint64)(InteractiveRandom())) << 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->server_hash_bits = seeds[0];
|
||||||
|
this->rcon_hash_bits = seeds[1];
|
||||||
|
this->settings_hash_bits = seeds[2];
|
||||||
|
|
||||||
/* The Socket and Info pools need to be the same in size. After all,
|
/* The Socket and Info pools need to be the same in size. After all,
|
||||||
* each Socket will be associated with at most one Info object. As
|
* each Socket will be associated with at most one Info object. As
|
||||||
@@ -472,7 +491,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword()
|
|||||||
this->last_frame = this->last_frame_server = _frame_counter;
|
this->last_frame = this->last_frame_server = _frame_counter;
|
||||||
|
|
||||||
Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD, SHRT_MAX);
|
Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD, SHRT_MAX);
|
||||||
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits);
|
p->Send_uint64(this->server_hash_bits);
|
||||||
p->Send_string(_settings_client.network.network_id);
|
p->Send_string(_settings_client.network.network_id);
|
||||||
this->SendPacket(p);
|
this->SendPacket(p);
|
||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
@@ -512,9 +531,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome()
|
|||||||
p = new Packet(PACKET_SERVER_WELCOME, SHRT_MAX);
|
p = new Packet(PACKET_SERVER_WELCOME, SHRT_MAX);
|
||||||
p->Send_uint32(this->client_id);
|
p->Send_uint32(this->client_id);
|
||||||
p->Send_uint32(_settings_game.game_creation.generation_seed);
|
p->Send_uint32(_settings_game.game_creation.generation_seed);
|
||||||
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits);
|
p->Send_uint64(this->server_hash_bits);
|
||||||
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->rcon_hash_bits);
|
p->Send_uint64(this->rcon_hash_bits);
|
||||||
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->settings_hash_bits);
|
p->Send_uint64(this->settings_hash_bits);
|
||||||
p->Send_string(_settings_client.network.network_id);
|
p->Send_string(_settings_client.network.network_id);
|
||||||
p->Send_string(_network_company_server_id);
|
p->Send_string(_network_company_server_id);
|
||||||
this->SendPacket(p);
|
this->SendPacket(p);
|
||||||
@@ -963,11 +982,11 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(P
|
|||||||
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH);
|
std::vector<byte> password = p->Recv_buffer();
|
||||||
|
|
||||||
/* Check game password. Allow joining if we cleared the password meanwhile */
|
/* Check game password. Allow joining if we cleared the password meanwhile */
|
||||||
if (!_settings_client.network.server_password.empty() &&
|
if (!_settings_client.network.server_password.empty() &&
|
||||||
password != GenerateCompanyPasswordHash(_settings_client.network.server_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->server_hash_bits)) {
|
password != this->game_password_hash_cache.GetHash(_settings_client.network.server_password, this->server_hash_bits)) {
|
||||||
/* Password is invalid */
|
/* Password is invalid */
|
||||||
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
|
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
|
||||||
}
|
}
|
||||||
@@ -1009,14 +1028,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWO
|
|||||||
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH);
|
std::vector<byte> password = p->Recv_buffer();
|
||||||
|
|
||||||
/* Check settings password. Deny if no password is set */
|
/* Check settings password. Deny if no password is set */
|
||||||
if (password.empty()) {
|
if (password.empty()) {
|
||||||
if (this->settings_authed) DEBUG(net, 0, "[settings-ctrl] client-id %d deauthed", this->client_id);
|
if (this->settings_authed) DEBUG(net, 0, "[settings-ctrl] client-id %d deauthed", this->client_id);
|
||||||
this->settings_authed = false;
|
this->settings_authed = false;
|
||||||
} else if (_settings_client.network.settings_password.empty() ||
|
} else if (_settings_client.network.settings_password.empty() ||
|
||||||
password != GenerateCompanyPasswordHash(_settings_client.network.settings_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->settings_hash_bits)) {
|
password != this->settings_password_hash_cache.GetHash(_settings_client.network.settings_password, this->settings_hash_bits)) {
|
||||||
DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id);
|
DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id);
|
||||||
NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied");
|
NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied");
|
||||||
this->settings_authed = false;
|
this->settings_authed = false;
|
||||||
@@ -1576,10 +1595,10 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet *p)
|
|||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH);
|
std::vector<byte> password = p->Recv_buffer();
|
||||||
std::string command = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH);
|
std::string command = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH);
|
||||||
|
|
||||||
if (password != GenerateCompanyPasswordHash(_settings_client.network.rcon_password.c_str(), _settings_client.network.network_id.c_str(), _settings_game.game_creation.generation_seed ^ this->rcon_hash_bits)) {
|
if (password != this->rcon_password_hash_cache.GetHash(_settings_client.network.rcon_password, this->rcon_hash_bits)) {
|
||||||
DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id);
|
DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id);
|
||||||
NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied");
|
NetworkServerSendRcon(this->client_id, CC_ERROR, "Access Denied");
|
||||||
return NETWORK_RECV_STATUS_OKAY;
|
return NETWORK_RECV_STATUS_OKAY;
|
||||||
|
@@ -22,6 +22,16 @@ extern NetworkClientSocketPool _networkclientsocket_pool;
|
|||||||
|
|
||||||
/** Class for handling the server side of the game connection. */
|
/** Class for handling the server side of the game connection. */
|
||||||
class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
|
class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<&_networkclientsocket_pool>, public NetworkGameSocketHandler, public TCPListenHandler<ServerNetworkGameSocketHandler, PACKET_SERVER_FULL, PACKET_SERVER_BANNED> {
|
||||||
|
struct CachedPassword {
|
||||||
|
std::string source;
|
||||||
|
std::vector<byte> cached_hash;
|
||||||
|
|
||||||
|
const std::vector<byte> &GetHash(const std::string &password, uint64 password_game_seed);
|
||||||
|
};
|
||||||
|
CachedPassword game_password_hash_cache;
|
||||||
|
CachedPassword rcon_password_hash_cache;
|
||||||
|
CachedPassword settings_password_hash_cache;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
NetworkRecvStatus Receive_CLIENT_JOIN(Packet *p) override;
|
NetworkRecvStatus Receive_CLIENT_JOIN(Packet *p) override;
|
||||||
NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet *p) override;
|
NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet *p) override;
|
||||||
@@ -76,9 +86,9 @@ public:
|
|||||||
ClientStatus status; ///< Status of this client
|
ClientStatus status; ///< Status of this client
|
||||||
CommandQueue outgoing_queue; ///< The command-queue awaiting delivery
|
CommandQueue outgoing_queue; ///< The command-queue awaiting delivery
|
||||||
size_t receive_limit; ///< Amount of bytes that we can receive at this moment
|
size_t receive_limit; ///< Amount of bytes that we can receive at this moment
|
||||||
uint32 server_hash_bits; ///< Server password hash entropy bits
|
uint64 server_hash_bits; ///< Server password hash entropy bits
|
||||||
uint32 rcon_hash_bits; ///< Rcon password hash entropy bits
|
uint64 rcon_hash_bits; ///< Rcon password hash entropy bits
|
||||||
uint32 settings_hash_bits; ///< Settings password hash entropy bits
|
uint64 settings_hash_bits; ///< Settings password hash entropy bits
|
||||||
bool settings_authed = false;///< Authorised to control all game settings
|
bool settings_authed = false;///< Authorised to control all game settings
|
||||||
bool supports_zstd = false; ///< Client supports zstd compression
|
bool supports_zstd = false; ///< Client supports zstd compression
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user