Allow for custom chunk/entity view distance and compression threshold without recompiling the server.

This commit is contained in:
themode 2020-10-25 16:48:14 +01:00
parent 424e99e2d8
commit 1b7613d977
14 changed files with 204 additions and 96 deletions

View File

@ -90,9 +90,6 @@ public class MinecraftServer {
public static final int THREAD_COUNT_PARALLEL_CHUNK_SAVING = 4;
// Config
public static final int CHUNK_VIEW_DISTANCE = 10;
public static final int ENTITY_VIEW_DISTANCE = 5;
public static final int COMPRESSION_THRESHOLD = 256;
// Can be modified at performance cost when increased
public static final int TICK_PER_SECOND = 20;
private static final int MS_TO_SEC = 1000;
@ -137,6 +134,11 @@ public class MinecraftServer {
private static MinecraftServer minecraftServer;
// Data
private static boolean started;
private static int chunkViewDistance = 10;
private static int entityViewDistance = 5;
private static int compressionThreshold = 256;
private static ResponseDataConsumer responseDataConsumer;
private static String brandName = "Minestom";
private static Difficulty difficulty = Difficulty.NORMAL;
@ -392,6 +394,81 @@ public class MinecraftServer {
return connectionManager;
}
/**
* Gets if the server is up and running.
*
* @return true if the server is started
*/
public static boolean isStarted() {
return started;
}
/**
* Gets the chunk view distance of the server.
*
* @return the chunk view distance
*/
public static int getChunkViewDistance() {
return chunkViewDistance;
}
/**
* Changes the chunk view distance of the server.
* <p>
* WARNING: this need to be called before {@link #start(String, int, ResponseDataConsumer)}.
*
* @param chunkViewDistance the new chunk view distance
* @throws IllegalStateException if this is called after the server started
*/
public static void setChunkViewDistance(int chunkViewDistance) {
Check.stateCondition(started, "The chunk view distance cannot be changed after the server has been started.");
MinecraftServer.chunkViewDistance = chunkViewDistance;
}
/**
* Gets the entity view distance of the server.
*
* @return the entity view distance
*/
public static int getEntityViewDistance() {
return entityViewDistance;
}
/**
* Changes the entity view distance of the server.
* <p>
* WARNING: this need to be called before {@link #start(String, int, ResponseDataConsumer)}.
*
* @param entityViewDistance the new entity view distance
* @throws IllegalStateException if this is called after the server started
*/
public static void setEntityViewDistance(int entityViewDistance) {
Check.stateCondition(started, "The entity view distance cannot be changed after the server has been started.");
MinecraftServer.entityViewDistance = entityViewDistance;
}
/**
* Gets the compression threshold of the server.
*
* @return the compression threshold, 0 means that compression is disabled
*/
public static int getCompressionThreshold() {
return compressionThreshold;
}
/**
* Changes the compression threshold of the server.
* <p>
* WARNING: this need to be called before {@link #start(String, int, ResponseDataConsumer)}.
*
* @param compressionThreshold the new compression threshold, 0 to disable compression
* @throws IllegalStateException if this is called after the server started
*/
public static void setCompressionThreshold(int compressionThreshold) {
Check.stateCondition(started, "The compression threshold cannot be changed after the server has been started.");
MinecraftServer.compressionThreshold = compressionThreshold;
}
/**
* Gets the consumer executed to show server-list data.
*
@ -485,6 +562,8 @@ public class MinecraftServer {
LOGGER.info("Extensions loaded in " + (t1 + System.nanoTime()) / 1_000_000D + "ms");
LOGGER.info("Minestom server started successfully.");
MinecraftServer.started = true;
}
/**

View File

@ -1032,8 +1032,9 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer {
((Player) this).onChunkChange(newChunk); // Refresh loaded chunk
// Refresh entity viewable list
final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), MinecraftServer.ENTITY_VIEW_DISTANCE);
final long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), MinecraftServer.ENTITY_VIEW_DISTANCE);
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), entityViewDistance);
final long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), entityViewDistance);
// Remove from previous chunks
final int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity);

View File

@ -195,7 +195,7 @@ public class Player extends LivingEntity implements CommandSender {
joinGamePacket.dimensionType = dimensionType;
joinGamePacket.maxPlayers = 0; // Unused
joinGamePacket.levelType = levelType;
joinGamePacket.viewDistance = MinecraftServer.CHUNK_VIEW_DISTANCE;
joinGamePacket.viewDistance = MinecraftServer.getChunkViewDistance();
joinGamePacket.reducedDebugInfo = false;
playerConnection.sendPacket(joinGamePacket);
@ -2124,11 +2124,11 @@ public class Player extends LivingEntity implements CommandSender {
/**
* @return the chunk range of the viewers,
* which is {@link MinecraftServer#CHUNK_VIEW_DISTANCE} or {@link PlayerSettings#getViewDistance()}
* which is {@link MinecraftServer#getChunkViewDistance()} or {@link PlayerSettings#getViewDistance()}
* based on which one is the lowest
*/
public int getChunkRange() {
final int serverRange = MinecraftServer.CHUNK_VIEW_DISTANCE;
final int serverRange = MinecraftServer.getChunkViewDistance();
final int playerRange = getSettings().viewDistance;
if (playerRange == 0) {
return serverRange; // Didn't receive settings packet yet (is the case on login)

View File

@ -2,6 +2,7 @@ package net.minestom.server.extras.mojangAuth;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
@ -10,83 +11,86 @@ import java.io.UnsupportedEncodingException;
import java.security.*;
public class MojangCrypt {
private static final Logger LOGGER = LogManager.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
return keyGen.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
LOGGER.error("Key pair generation failed!");
return null;
}
}
@Nullable
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
return keyGen.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
LOGGER.error("Key pair generation failed!");
return null;
}
}
public static byte[] digestData(String data, PublicKey publicKey, SecretKey secretKey) {
try {
return digestData("SHA-1", data.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
@Nullable
public static byte[] digestData(String data, PublicKey publicKey, SecretKey secretKey) {
try {
return digestData("SHA-1", data.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
private static byte[] digestData(String algorithm, byte[]... data) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
@Nullable
private static byte[] digestData(String algorithm, byte[]... data) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
for(byte[] bytes : data) {
digest.update(bytes);
}
for (byte[] bytes : data) {
digest.update(bytes);
}
return digest.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
return digest.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static SecretKey decryptByteToSecretKey(PrivateKey privateKey, byte[] bytes) {
return new SecretKeySpec(decryptUsingKey(privateKey, bytes), "AES");
}
public static SecretKey decryptByteToSecretKey(PrivateKey privateKey, byte[] bytes) {
return new SecretKeySpec(decryptUsingKey(privateKey, bytes), "AES");
}
public static byte[] decryptUsingKey(Key key, byte[] bytes) {
return cipherData(2, key, bytes);
}
public static byte[] decryptUsingKey(Key key, byte[] bytes) {
return cipherData(2, key, bytes);
}
private static byte[] cipherData(int mode, Key key, byte[] data) {
try {
return setupCipher(mode, key.getAlgorithm(), key).doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException var4) {
var4.printStackTrace();
}
private static byte[] cipherData(int mode, Key key, byte[] data) {
try {
return setupCipher(mode, key.getAlgorithm(), key).doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException var4) {
var4.printStackTrace();
}
LOGGER.error("Cipher data failed!");
return null;
}
LOGGER.error("Cipher data failed!");
return null;
}
private static Cipher setupCipher(int mode, String transformation, Key key) {
try {
Cipher cipher4 = Cipher.getInstance(transformation);
cipher4.init(mode, key);
return cipher4;
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException var4) {
var4.printStackTrace();
}
private static Cipher setupCipher(int mode, String transformation, Key key) {
try {
Cipher cipher4 = Cipher.getInstance(transformation);
cipher4.init(mode, key);
return cipher4;
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException var4) {
var4.printStackTrace();
}
LOGGER.error("Cipher creation failed!");
return null;
}
LOGGER.error("Cipher creation failed!");
return null;
}
public static Cipher getCipher(int mode, Key key) {
try {
Cipher cipher3 = Cipher.getInstance("AES/CFB8/NoPadding");
cipher3.init(mode, key, new IvParameterSpec(key.getEncoded()));
return cipher3;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
public static Cipher getCipher(int mode, Key key) {
try {
Cipher cipher3 = Cipher.getInstance("AES/CFB8/NoPadding");
cipher3.init(mode, key, new IvParameterSpec(key.getEncoded()));
return cipher3;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -824,7 +824,8 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
}
AddEntityToInstanceEvent event = new AddEntityToInstanceEvent(this, entity);
callCancellableEvent(AddEntityToInstanceEvent.class, event, () -> {
final long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entity.getPosition(), MinecraftServer.ENTITY_VIEW_DISTANCE);
final Position entityPosition = entity.getPosition();
final long[] visibleChunksEntity = ChunkUtils.getChunksInRange(entityPosition, MinecraftServer.getEntityViewDistance());
final boolean isPlayer = entity instanceof Player;
if (isPlayer) {
@ -846,7 +847,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta
});
}
final Position entityPosition = entity.getPosition();
final Chunk chunk = getChunkAt(entityPosition);
Check.notNull(chunk, "You tried to spawn an entity in an unloaded chunk, " + entityPosition);
addEntityToChunk(entity, chunk);

View File

@ -216,7 +216,7 @@ public final class ConnectionManager {
* Adds a new {@link Player} in the players list.
* Is currently used at
* {@link LoginStartPacket#process(PlayerConnection)}
* and in {@link FakePlayer#initPlayer(UUID, String, Consumer)}
* and in {@link FakePlayer#initPlayer(UUID, String, Consumer)}.
*
* @param player the player to add
*/

View File

@ -3,15 +3,16 @@ package net.minestom.server.network.packet.client;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.PlayerConnection;
import org.jetbrains.annotations.NotNull;
public interface ClientPreplayPacket extends ClientPacket {
ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
/**
* Called when this packet is received
* Called when the packet is received.
*
* @param connection the connection who sent the packet
*/
void process(PlayerConnection connection);
void process(@NotNull PlayerConnection connection);
}

View File

@ -9,6 +9,7 @@ import net.minestom.server.network.packet.server.login.LoginDisconnect;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
public class HandshakePacket implements ClientPreplayPacket {
@ -31,7 +32,7 @@ public class HandshakePacket implements ClientPreplayPacket {
}
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
switch (nextState) {
case 1:
connection.setConnectionState(ConnectionState.STATUS);

View File

@ -11,6 +11,7 @@ import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
import javax.crypto.SecretKey;
import java.math.BigInteger;
@ -20,28 +21,45 @@ import java.util.concurrent.atomic.AtomicInteger;
public class EncryptionResponsePacket implements ClientPreplayPacket {
private final static String THREAD_NAME = "Mojang Auth Thread";
private static AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
private byte[] sharedSecret;
private byte[] verifyToken;
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
// Encryption is only support for netty connection
if (!(connection instanceof NettyPlayerConnection)) {
return;
}
new Thread(THREAD_NAME + " #" + UNIQUE_THREAD_ID.incrementAndGet()) {
public void run() {
try {
if (!Arrays.equals(connection.getNonce(), getNonce())) {
System.out.println(connection.getLoginUsername() + " tried to login with an invalid nonce!");
MinecraftServer.getLOGGER().error(connection.getLoginUsername() + " tried to login with an invalid nonce!");
return;
}
if (!connection.getLoginUsername().isEmpty()) {
final String string3 = new BigInteger(MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey())).toString(16);
final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, connection.getLoginUsername()), string3);
((NettyPlayerConnection) connection).setEncryptionKey(getSecretKey());
final int threshold = MinecraftServer.COMPRESSION_THRESHOLD;
final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection;
if (threshold > 0 && connection instanceof NettyPlayerConnection) {
((NettyPlayerConnection) connection).enableCompression(threshold);
final byte[] digestedData = MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey());
if (digestedData == null) {
// Incorrect key, probably because of the client
MinecraftServer.getLOGGER().error("Connection " + nettyConnection.getRemoteAddress() + " failed initializing encryption.");
connection.disconnect();
return;
}
final String string3 = new BigInteger(digestedData).toString(16);
final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, connection.getLoginUsername()), string3);
nettyConnection.setEncryptionKey(getSecretKey());
final int threshold = MinecraftServer.getCompressionThreshold();
if (threshold > 0) {
nettyConnection.enableCompression(threshold);
}
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(gameProfile.getId(), gameProfile.getName());

View File

@ -12,6 +12,7 @@ import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@ -23,7 +24,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
public String username;
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
if (MojangAuth.isUsingMojangAuth()) {
if (CONNECTION_MANAGER.getPlayer(username) != null) {
connection.sendPacket(new LoginDisconnect(ALREADY_CONNECTED_JSON));
@ -38,7 +39,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
} else {
final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
final int threshold = MinecraftServer.COMPRESSION_THRESHOLD;
final int threshold = MinecraftServer.getCompressionThreshold();
if (threshold > 0 && connection instanceof NettyPlayerConnection) {
((NettyPlayerConnection) connection).enableCompression(threshold);

View File

@ -3,13 +3,14 @@ package net.minestom.server.network.packet.client.status;
import net.minestom.server.network.packet.client.ClientPreplayPacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
public class LegacyServerListPingPacket implements ClientPreplayPacket {
private byte payload;
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
}

View File

@ -4,13 +4,14 @@ import net.minestom.server.network.packet.client.ClientPreplayPacket;
import net.minestom.server.network.packet.server.status.PongPacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
public class PingPacket implements ClientPreplayPacket {
private long number;
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
PongPacket pongPacket = new PongPacket(number);
connection.sendPacket(pongPacket);
connection.disconnect();

View File

@ -7,11 +7,12 @@ import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.ping.ResponseData;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.utils.binary.BinaryReader;
import org.jetbrains.annotations.NotNull;
public class StatusRequestPacket implements ClientPreplayPacket {
@Override
public void process(PlayerConnection connection) {
public void process(@NotNull PlayerConnection connection) {
ResponseDataConsumer consumer = MinecraftServer.getResponseDataConsumer();
ResponseData responseData = new ResponseData();

View File

@ -23,7 +23,7 @@ public final class EntityUtils {
final Chunk chunk = ent1.getInstance().getChunkAt(ent1.getPosition());
long[] visibleChunksEntity = ChunkUtils.getChunksInRange(ent2.getPosition(), MinecraftServer.ENTITY_VIEW_DISTANCE);
final long[] visibleChunksEntity = ChunkUtils.getChunksInRange(ent2.getPosition(), MinecraftServer.getEntityViewDistance());
for (long visibleChunk : visibleChunksEntity) {
final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk);
final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk);