mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 09:51:35 +01:00
commit
1181724b7a
@ -107,12 +107,6 @@ dependencies {
|
||||
// Only here to ensure J9 module support for extensions and our classloaders
|
||||
testCompileOnly 'org.mockito:mockito-core:3.11.1'
|
||||
|
||||
// Netty
|
||||
api 'io.netty:netty-handler:4.1.65.Final'
|
||||
api 'io.netty:netty-codec:4.1.65.Final'
|
||||
api 'io.netty:netty-transport-native-epoll:4.1.65.Final:linux-x86_64'
|
||||
api 'io.netty:netty-transport-native-kqueue:4.1.65.Final:osx-x86_64'
|
||||
|
||||
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
|
||||
api 'it.unimi.dsi:fastutil:8.5.4'
|
||||
|
||||
|
@ -21,10 +21,10 @@ import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.monitoring.BenchmarkManager;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
|
||||
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateViewDistancePacket;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.ping.ResponseDataConsumer;
|
||||
import net.minestom.server.recipe.RecipeManager;
|
||||
import net.minestom.server.scoreboard.TeamManager;
|
||||
@ -44,6 +44,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* The main server class used to start the server and retrieve all the managers.
|
||||
* <p>
|
||||
@ -85,9 +88,7 @@ public final class MinecraftServer {
|
||||
// Network
|
||||
private static PacketListenerManager packetListenerManager;
|
||||
private static PacketProcessor packetProcessor;
|
||||
private static NettyServer nettyServer;
|
||||
private static int nettyThreadCount = Runtime.getRuntime().availableProcessors();
|
||||
private static boolean processNettyErrors = true;
|
||||
private static Server server;
|
||||
|
||||
private static ExceptionManager exceptionManager;
|
||||
|
||||
@ -122,7 +123,6 @@ public final class MinecraftServer {
|
||||
private static int chunkViewDistance = 8;
|
||||
private static int entityViewDistance = 5;
|
||||
private static int compressionThreshold = 256;
|
||||
private static boolean packetCaching = true;
|
||||
private static boolean groupedPacket = true;
|
||||
private static boolean terminalEnabled = System.getProperty("minestom.terminal.disabled") == null;
|
||||
private static ResponseDataConsumer responseDataConsumer;
|
||||
@ -168,7 +168,11 @@ public final class MinecraftServer {
|
||||
|
||||
tagManager = new TagManager();
|
||||
|
||||
nettyServer = new NettyServer(packetProcessor);
|
||||
try {
|
||||
server = new Server(packetProcessor);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
@ -277,16 +281,6 @@ public final class MinecraftServer {
|
||||
return packetListenerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the netty server.
|
||||
*
|
||||
* @return the netty server
|
||||
*/
|
||||
public static NettyServer getNettyServer() {
|
||||
checkInitStatus(nettyServer);
|
||||
return nettyServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the manager handling all registered instances.
|
||||
*
|
||||
@ -519,34 +513,6 @@ public final class MinecraftServer {
|
||||
MinecraftServer.compressionThreshold = compressionThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the packet caching feature is enabled.
|
||||
* <p>
|
||||
* This feature allows some packets (implementing the {@link net.minestom.server.utils.cache.CacheablePacket} to be cached
|
||||
* in order to do not have to be written and compressed over and over again), this is especially useful for chunk and light packets.
|
||||
* <p>
|
||||
* It is enabled by default and it is our recommendation,
|
||||
* you should only disable it if you want to focus on low memory usage
|
||||
* at the cost of many packet writing and compression.
|
||||
*
|
||||
* @return true if the packet caching feature is enabled, false otherwise
|
||||
*/
|
||||
public static boolean hasPacketCaching() {
|
||||
return packetCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable packet caching.
|
||||
*
|
||||
* @param packetCaching true to enable packet caching
|
||||
* @throws IllegalStateException if this is called after the server started
|
||||
* @see #hasPacketCaching()
|
||||
*/
|
||||
public static void setPacketCaching(boolean packetCaching) {
|
||||
Check.stateCondition(started, "You cannot change the packet caching value after the server has been started.");
|
||||
MinecraftServer.packetCaching = packetCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the packet caching feature is enabled.
|
||||
* <p>
|
||||
@ -666,45 +632,9 @@ public final class MinecraftServer {
|
||||
return updateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of threads used by Netty.
|
||||
* <p>
|
||||
* Is the number of vCPU by default.
|
||||
*
|
||||
* @return the number of netty threads
|
||||
*/
|
||||
public static int getNettyThreadCount() {
|
||||
return nettyThreadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the number of threads used by Netty.
|
||||
*
|
||||
* @param nettyThreadCount the number of threads
|
||||
* @throws IllegalStateException if the server is already started
|
||||
*/
|
||||
public static void setNettyThreadCount(int nettyThreadCount) {
|
||||
Check.stateCondition(started, "Netty thread count can only be changed before the server starts!");
|
||||
MinecraftServer.nettyThreadCount = nettyThreadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the server should process netty errors and other unnecessary netty events.
|
||||
*
|
||||
* @return should process netty errors
|
||||
*/
|
||||
public static boolean shouldProcessNettyErrors() {
|
||||
return processNettyErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the server should process netty errors and other unnecessary netty events.
|
||||
* false is faster
|
||||
*
|
||||
* @param processNettyErrors should process netty errors
|
||||
*/
|
||||
public static void setShouldProcessNettyErrors(boolean processNettyErrors) {
|
||||
MinecraftServer.processNettyErrors = processNettyErrors;
|
||||
public static Server getServer() {
|
||||
checkInitStatus(server);
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -744,8 +674,11 @@ public final class MinecraftServer {
|
||||
updateManager.start();
|
||||
|
||||
// Init & start the TCP server
|
||||
nettyServer.init();
|
||||
nettyServer.start(address, port);
|
||||
try {
|
||||
server.start(new InetSocketAddress(address, port));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (extensionManager.shouldLoadOnStartup()) {
|
||||
final long loadStartTime = System.nanoTime();
|
||||
@ -779,7 +712,7 @@ public final class MinecraftServer {
|
||||
updateManager.stop();
|
||||
schedulerManager.shutdown();
|
||||
connectionManager.shutdown();
|
||||
nettyServer.stop();
|
||||
server.stop();
|
||||
storageManager.getLoadedLocations().forEach(StorageLocation::close);
|
||||
LOGGER.info("Unloading all extensions.");
|
||||
extensionManager.shutdown();
|
||||
|
@ -1,15 +1,15 @@
|
||||
package net.minestom.server;
|
||||
|
||||
import net.minestom.server.acquirable.Acquirable;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.monitoring.TickMonitor;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.thread.SingleThreadProvider;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@ -39,13 +39,13 @@ public final class UpdateManager {
|
||||
/**
|
||||
* Should only be created in MinecraftServer.
|
||||
*/
|
||||
protected UpdateManager() {
|
||||
UpdateManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server loop in the update thread.
|
||||
*/
|
||||
protected void start() {
|
||||
void start() {
|
||||
final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
||||
|
||||
new Thread(() -> {
|
||||
@ -82,10 +82,12 @@ public final class UpdateManager {
|
||||
}
|
||||
|
||||
// Flush all waiting packets
|
||||
AsyncUtils.runAsync(() -> connectionManager.getOnlinePlayers().parallelStream()
|
||||
.filter(player -> player.getPlayerConnection() instanceof NettyPlayerConnection)
|
||||
.map(player -> (NettyPlayerConnection) player.getPlayerConnection())
|
||||
.forEach(NettyPlayerConnection::flush));
|
||||
for (Player player : connectionManager.getOnlinePlayers()) {
|
||||
final var connection = player.getPlayerConnection();
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
((PlayerSocketConnection) connection).flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable thread until next tick
|
||||
LockSupport.parkNanos((long) ((MinecraftServer.TICK_MS * 1e6) - tickTime));
|
||||
@ -93,6 +95,7 @@ public final class UpdateManager {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
this.threadProvider.shutdown();
|
||||
}, MinecraftServer.THREAD_NAME_TICK_SCHEDULER).start();
|
||||
}
|
||||
|
||||
@ -239,6 +242,5 @@ public final class UpdateManager {
|
||||
*/
|
||||
public void stop() {
|
||||
this.stopRequested = true;
|
||||
this.threadProvider.shutdown();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.command.builder.arguments;
|
||||
|
||||
import io.netty.util.internal.StringUtil;
|
||||
import net.minestom.server.command.builder.NodeMaker;
|
||||
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
||||
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
|
||||
@ -48,11 +47,10 @@ public class ArgumentString extends Argument<String> {
|
||||
*/
|
||||
@Deprecated
|
||||
public static String staticParse(@NotNull String input) throws ArgumentSyntaxException {
|
||||
|
||||
// Return if not quoted
|
||||
if (!input.contains(String.valueOf(DOUBLE_QUOTE)) &&
|
||||
!input.contains(String.valueOf(QUOTE)) &&
|
||||
!input.contains(String.valueOf(StringUtil.SPACE))) {
|
||||
!input.contains(StringUtils.SPACE)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import net.minestom.server.potion.TimedPotion;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagHandler;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.entity.EntityUtils;
|
||||
import net.minestom.server.utils.player.PlayerUtils;
|
||||
@ -490,14 +491,14 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
}
|
||||
|
||||
private void velocityTick() {
|
||||
final boolean isNettyClient = PlayerUtils.isNettyClient(this);
|
||||
final boolean isSocketClient = PlayerUtils.isSocketClient(this);
|
||||
final boolean noGravity = hasNoGravity();
|
||||
final boolean hasVelocity = hasVelocity();
|
||||
boolean applyVelocity;
|
||||
// Non-player entities with either velocity or gravity enabled
|
||||
applyVelocity = !isNettyClient && (hasVelocity || !noGravity);
|
||||
applyVelocity = !isSocketClient && (hasVelocity || !noGravity);
|
||||
// Players with a velocity applied (client is responsible for gravity)
|
||||
applyVelocity |= isNettyClient && hasVelocity;
|
||||
applyVelocity |= isSocketClient && hasVelocity;
|
||||
if (!applyVelocity) {
|
||||
return;
|
||||
}
|
||||
@ -536,13 +537,13 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
return;
|
||||
}
|
||||
refreshPosition(finalVelocityPosition, true);
|
||||
if (!isNettyClient) {
|
||||
if (!isSocketClient) {
|
||||
synchronizePosition(true);
|
||||
}
|
||||
|
||||
// Update velocity
|
||||
if (hasVelocity || !newVelocity.isZero()) {
|
||||
if (onGround && isNettyClient) {
|
||||
if (onGround && isSocketClient) {
|
||||
// Stop player velocity
|
||||
this.velocity = Vec.ZERO;
|
||||
} else {
|
||||
@ -708,6 +709,7 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
||||
Check.stateCondition(!instance.isRegistered(),
|
||||
"Instances need to be registered, please use InstanceManager#registerInstance or InstanceManager#registerSharedInstance");
|
||||
if (isRemoved()) return AsyncUtils.VOID_FUTURE;
|
||||
if (this.instance != null) {
|
||||
this.instance.UNSAFE_removeEntity(this);
|
||||
}
|
||||
@ -1267,16 +1269,15 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
* WARNING: this does not trigger {@link EntityDeathEvent}.
|
||||
*/
|
||||
public void remove() {
|
||||
if (isRemoved())
|
||||
return;
|
||||
|
||||
if (isRemoved()) return;
|
||||
MinecraftServer.getUpdateManager().getThreadProvider().removeEntity(this);
|
||||
this.removed = true;
|
||||
this.shouldRemove = true;
|
||||
Entity.ENTITY_BY_ID.remove(id);
|
||||
Entity.ENTITY_BY_UUID.remove(uuid);
|
||||
if (instance != null)
|
||||
if (instance != null) {
|
||||
instance.UNSAFE_removeEntity(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,8 +55,8 @@ import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.recipe.Recipe;
|
||||
import net.minestom.server.recipe.RecipeManager;
|
||||
import net.minestom.server.resourcepack.ResourcePack;
|
||||
@ -92,7 +92,7 @@ import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Those are the major actors of the server,
|
||||
* they are not necessary backed by a {@link NettyPlayerConnection} as shown by {@link FakePlayer}.
|
||||
* they are not necessary backed by a {@link PlayerSocketConnection} as shown by {@link FakePlayer}.
|
||||
* <p>
|
||||
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
|
||||
*/
|
||||
@ -212,7 +212,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
* Init the player and spawn him.
|
||||
* <p>
|
||||
* WARNING: executed in the main update thread
|
||||
* UNSAFE: Only meant to be used when a netty player connects through the server.
|
||||
* UNSAFE: Only meant to be used when a socket player connects through the server.
|
||||
*
|
||||
* @param spawnInstance the player spawn instance (defined in {@link PlayerLoginEvent})
|
||||
*/
|
||||
@ -441,18 +441,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (isRemoved())
|
||||
return;
|
||||
|
||||
if (isRemoved()) return;
|
||||
EventDispatcher.call(new PlayerDisconnectEvent(this));
|
||||
|
||||
super.remove();
|
||||
this.packets.clear();
|
||||
if (getOpenInventory() != null)
|
||||
if (getOpenInventory() != null) {
|
||||
getOpenInventory().removeViewer(this);
|
||||
|
||||
}
|
||||
MinecraftServer.getBossBarManager().removeAllBossBars(this);
|
||||
|
||||
// Advancement tabs cache
|
||||
{
|
||||
Set<AdvancementTab> advancementTabs = AdvancementTab.getTabs(this);
|
||||
@ -462,15 +458,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all viewable entities
|
||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||
// Clear all viewable chunks
|
||||
this.viewableChunks.forEach(chunk -> {
|
||||
if (chunk.isLoaded())
|
||||
chunk.removeViewer(this);
|
||||
});
|
||||
playerConnection.disconnect();
|
||||
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1376,8 +1367,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
} else {
|
||||
disconnectPacket = new DisconnectPacket(component);
|
||||
}
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).writeAndFlush(disconnectPacket);
|
||||
if (playerConnection instanceof PlayerSocketConnection) {
|
||||
((PlayerSocketConnection) playerConnection).writeAndFlush(disconnectPacket);
|
||||
playerConnection.disconnect();
|
||||
} else {
|
||||
playerConnection.sendPacket(disconnectPacket);
|
||||
|
@ -22,7 +22,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A fake player will behave exactly the same way as would do a {@link Player} backed by a netty connection
|
||||
* A fake player will behave exactly the same way as would do a {@link Player} backed by a socket connection
|
||||
* (events, velocity, gravity, player list, etc...) with the exception that you need to control it server-side
|
||||
* using a {@link FakePlayerController} (see {@link #getController()}).
|
||||
* <p>
|
||||
|
@ -8,10 +8,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.security.KeyPair;
|
||||
|
||||
public final class MojangAuth {
|
||||
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private static KeyPair keyPair;
|
||||
private static volatile KeyPair keyPair;
|
||||
|
||||
/**
|
||||
* Enables mojang authentication on the server.
|
||||
@ -21,19 +19,16 @@ public final class MojangAuth {
|
||||
public static void init() {
|
||||
Check.stateCondition(enabled, "Mojang auth is already enabled!");
|
||||
Check.stateCondition(MinecraftServer.isStarted(), "The server has already been started!");
|
||||
|
||||
enabled = true;
|
||||
|
||||
MojangAuth.enabled = true;
|
||||
// Generate necessary fields...
|
||||
keyPair = MojangCrypt.generateKeyPair();
|
||||
MojangAuth.keyPair = MojangCrypt.generateKeyPair();
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static KeyPair getKeyPair() {
|
||||
public static @Nullable KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ public class OpenToLAN {
|
||||
* Performs the ping.
|
||||
*/
|
||||
private static void ping() {
|
||||
if (MinecraftServer.getNettyServer().getPort() != 0) {
|
||||
if (MinecraftServer.getServer().getPort() != 0) {
|
||||
if (packet == null || eventCooldown.isReady(System.currentTimeMillis())) {
|
||||
final ServerListPingEvent event = new ServerListPingEvent(OPEN_TO_LAN);
|
||||
EventDispatcher.call(event);
|
||||
|
@ -1,54 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
public class CipherBase {
|
||||
|
||||
private final Cipher cipher;
|
||||
private byte[] inTempArray = new byte[0];
|
||||
private byte[] outTempArray = new byte[0];
|
||||
|
||||
protected CipherBase(@NotNull Cipher cipher) {
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
private byte[] bufToByte(ByteBuf buffer) {
|
||||
int remainingBytes = buffer.readableBytes();
|
||||
|
||||
// Need to resize temp array
|
||||
if (inTempArray.length < remainingBytes) {
|
||||
inTempArray = new byte[remainingBytes];
|
||||
}
|
||||
|
||||
buffer.readBytes(inTempArray, 0, remainingBytes);
|
||||
return inTempArray;
|
||||
}
|
||||
|
||||
protected ByteBuf decrypt(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn) throws ShortBufferException {
|
||||
int remainingBytes = byteBufIn.readableBytes();
|
||||
byte[] bytes = bufToByte(byteBufIn);
|
||||
|
||||
ByteBuf outputBuffer = channelHandlerContext.alloc().heapBuffer(cipher.getOutputSize(remainingBytes));
|
||||
outputBuffer.writerIndex(cipher.update(bytes, 0, remainingBytes, outputBuffer.array(), outputBuffer.arrayOffset()));
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
protected void encrypt(ByteBuf byteBufIn, ByteBuf byteBufOut) throws ShortBufferException {
|
||||
int remainingBytes = byteBufIn.readableBytes();
|
||||
byte[] bytes = bufToByte(byteBufIn);
|
||||
int newSize = cipher.getOutputSize(remainingBytes);
|
||||
|
||||
// Need to resize temp array
|
||||
if (outTempArray.length < newSize) {
|
||||
outTempArray = new byte[newSize];
|
||||
}
|
||||
|
||||
byteBufOut.writeBytes(outTempArray, 0, cipher.update(bytes, 0, remainingBytes, outTempArray));
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.util.List;
|
||||
|
||||
public class Decrypter extends MessageToMessageDecoder<ByteBuf> {
|
||||
private final CipherBase cipher;
|
||||
|
||||
public Decrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
|
||||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
|
||||
list.add(this.cipher.decrypt(channelHandlerContext, byteBuf));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.minestom.server.extras.mojangAuth;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class Encrypter extends MessageToByteEncoder<ByteBuf> {
|
||||
private final CipherBase cipher;
|
||||
|
||||
public Encrypter(Cipher cipher) {
|
||||
this.cipher = new CipherBase(cipher);
|
||||
}
|
||||
|
||||
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception {
|
||||
this.cipher.encrypt(byteBufIn, byteBufOut);
|
||||
}
|
||||
}
|
@ -14,8 +14,7 @@ import java.security.*;
|
||||
public final class MojangCrypt {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@Nullable
|
||||
public static KeyPair generateKeyPair() {
|
||||
public static @Nullable KeyPair generateKeyPair() {
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(1024);
|
||||
@ -27,8 +26,7 @@ public final class MojangCrypt {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static byte[] digestData(String data, PublicKey publicKey, SecretKey secretKey) {
|
||||
public static byte @Nullable [] digestData(String data, PublicKey publicKey, SecretKey secretKey) {
|
||||
try {
|
||||
return digestData("SHA-1", data.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
@ -37,15 +35,12 @@ public final class MojangCrypt {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] digestData(String algorithm, byte[]... data) {
|
||||
private static byte @Nullable [] digestData(String algorithm, byte[]... data) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
|
||||
for (byte[] bytes : data) {
|
||||
digest.update(bytes);
|
||||
}
|
||||
|
||||
return digest.digest();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
@ -67,7 +62,6 @@ public final class MojangCrypt {
|
||||
} catch (IllegalBlockSizeException | BadPaddingException var4) {
|
||||
MinecraftServer.getExceptionManager().handleException(var4);
|
||||
}
|
||||
|
||||
LOGGER.error("Cipher data failed!");
|
||||
return null;
|
||||
}
|
||||
@ -80,7 +74,6 @@ public final class MojangCrypt {
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException var4) {
|
||||
MinecraftServer.getExceptionManager().handleException(var4);
|
||||
}
|
||||
|
||||
LOGGER.error("Cipher creation failed!");
|
||||
return null;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.extras.query;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
@ -23,6 +21,7 @@ import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
@ -151,18 +150,18 @@ public class Query {
|
||||
}
|
||||
|
||||
// get the contents
|
||||
ByteBuf data = Unpooled.wrappedBuffer(packet.getData());
|
||||
ByteBuffer data = ByteBuffer.wrap(packet.getData());
|
||||
|
||||
// check the magic field
|
||||
if (data.readUnsignedShort() != 0xFEFD) {
|
||||
if ((data.getShort() & 0xFFFF) != 0xFEFD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// now check the query type
|
||||
byte type = data.readByte();
|
||||
byte type = data.get();
|
||||
|
||||
if (type == 9) { // handshake
|
||||
int sessionID = data.readInt();
|
||||
int sessionID = data.getInt();
|
||||
int challengeToken = RANDOM.nextInt();
|
||||
|
||||
CHALLENGE_TOKENS.put(challengeToken, packet.getSocketAddress());
|
||||
@ -184,12 +183,12 @@ public class Query {
|
||||
}
|
||||
}
|
||||
} else if (type == 0) { // stat
|
||||
int sessionID = data.readInt();
|
||||
int challengeToken = data.readInt();
|
||||
int sessionID = data.getInt();
|
||||
int challengeToken = data.getInt();
|
||||
SocketAddress sender = packet.getSocketAddress();
|
||||
|
||||
if (CHALLENGE_TOKENS.containsKey(challengeToken) && CHALLENGE_TOKENS.get(challengeToken).equals(sender)) {
|
||||
int remaining = data.readableBytes();
|
||||
int remaining = data.remaining();
|
||||
|
||||
if (remaining == 0) { // basic
|
||||
BasicQueryEvent event = new BasicQueryEvent(sender, sessionID);
|
||||
|
@ -142,7 +142,7 @@ public class BasicQueryResponse implements Writeable {
|
||||
writer.writeNullTerminatedString(this.map, Query.CHARSET);
|
||||
writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET);
|
||||
writer.writeNullTerminatedString(this.maxPlayers, Query.CHARSET);
|
||||
writer.getBuffer().writeShortLE(MinecraftServer.getNettyServer().getPort());
|
||||
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), ""), Query.CHARSET);
|
||||
writer.getBuffer().putShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
|
||||
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ public enum QueryKey {
|
||||
MAP(() -> "world"),
|
||||
NUM_PLAYERS("numplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size())),
|
||||
MAX_PLAYERS("maxplayers", () -> String.valueOf(MinecraftServer.getConnectionManager().getOnlinePlayers().size() + 1)),
|
||||
HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getNettyServer().getPort())),
|
||||
HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getNettyServer().getAddress(), "localhost"));
|
||||
HOST_PORT("hostport", () -> String.valueOf(MinecraftServer.getServer().getPort())),
|
||||
HOST_IP("hostip", () -> Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), "localhost"));
|
||||
|
||||
static QueryKey[] VALUES = QueryKey.values();
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.extras.velocity;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.PlayerSkin;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
@ -10,6 +9,7 @@ import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -54,9 +54,11 @@ public final class VelocityProxy {
|
||||
|
||||
final byte[] signature = reader.readBytes(32);
|
||||
|
||||
ByteBuf buf = reader.getBuffer();
|
||||
final byte[] data = new byte[buf.readableBytes()];
|
||||
buf.getBytes(buf.readerIndex(), data);
|
||||
ByteBuffer buf = reader.getBuffer();
|
||||
buf.mark();
|
||||
final byte[] data = new byte[buf.remaining()];
|
||||
buf.get(data);
|
||||
buf.reset();
|
||||
|
||||
try {
|
||||
final Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
@ -13,18 +13,18 @@ import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockGetter;
|
||||
import net.minestom.server.instance.block.BlockSetter;
|
||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagHandler;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkSupplier;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
// TODO light data & API
|
||||
@ -126,11 +126,13 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
public abstract long getLastChangeTime();
|
||||
|
||||
/**
|
||||
* Creates a {@link ChunkDataPacket} with this chunk data ready to be written.
|
||||
* Sends the chunk data to {@code player}.
|
||||
*
|
||||
* @return a new chunk data packet
|
||||
* @param player the player
|
||||
*/
|
||||
public abstract @NotNull ChunkDataPacket createChunkPacket();
|
||||
public abstract void sendChunk(@NotNull Player player);
|
||||
|
||||
public abstract void sendChunk();
|
||||
|
||||
/**
|
||||
* Creates a copy of this chunk, including blocks state id, custom block id, biomes, update data.
|
||||
@ -152,7 +154,7 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
/**
|
||||
* Gets the unique identifier of this chunk.
|
||||
* <p>
|
||||
* WARNING: this UUID is not persistent but randomized once the object is instantiate.
|
||||
* WARNING: this UUID is not persistent but randomized once the object is instantiated.
|
||||
*
|
||||
* @return the chunk identifier
|
||||
*/
|
||||
@ -244,50 +246,6 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
this.columnarSpace = columnarSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the light packet of this chunk.
|
||||
*
|
||||
* @return the light packet
|
||||
*/
|
||||
@NotNull
|
||||
public UpdateLightPacket getLightPacket() {
|
||||
long skyMask = 0;
|
||||
long blockMask = 0;
|
||||
List<byte[]> skyLights = new ArrayList<>();
|
||||
List<byte[]> blockLights = new ArrayList<>();
|
||||
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime());
|
||||
updateLightPacket.chunkX = getChunkX();
|
||||
updateLightPacket.chunkZ = getChunkZ();
|
||||
|
||||
updateLightPacket.skyLight = skyLights;
|
||||
updateLightPacket.blockLight = blockLights;
|
||||
|
||||
final var sections = getSections();
|
||||
for (var entry : sections.entrySet()) {
|
||||
final int index = entry.getKey() + 1;
|
||||
final Section section = entry.getValue();
|
||||
|
||||
final var skyLight = section.getSkyLight();
|
||||
final var blockLight = section.getBlockLight();
|
||||
|
||||
if (!ArrayUtils.empty(skyLight)) {
|
||||
skyLights.add(skyLight);
|
||||
skyMask |= 1L << index;
|
||||
}
|
||||
if (!ArrayUtils.empty(blockLight)) {
|
||||
blockLights.add(blockLight);
|
||||
blockMask |= 1L << index;
|
||||
}
|
||||
}
|
||||
|
||||
updateLightPacket.skyLightMask = new long[]{skyMask};
|
||||
updateLightPacket.blockLightMask = new long[]{blockMask};
|
||||
updateLightPacket.emptySkyLightMask = new long[0];
|
||||
updateLightPacket.emptyBlockLightMask = new long[0];
|
||||
return updateLightPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify if the chunk should still be kept in memory.
|
||||
*
|
||||
@ -365,34 +323,10 @@ public abstract class Chunk implements BlockGetter, BlockSetter, Viewable, Ticka
|
||||
tag.write(nbt, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the chunk data to {@code player}.
|
||||
*
|
||||
* @param player the player
|
||||
*/
|
||||
public synchronized void sendChunk(@NotNull Player player) {
|
||||
// Only send loaded chunk
|
||||
if (!isLoaded())
|
||||
return;
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
playerConnection.sendPacket(getLightPacket());
|
||||
playerConnection.sendPacket(createChunkPacket());
|
||||
}
|
||||
|
||||
public synchronized void sendChunk() {
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
sendPacketToViewers(getLightPacket());
|
||||
sendPacketToViewers(createChunkPacket());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the chunk as "unloaded".
|
||||
*/
|
||||
protected void unload() {
|
||||
this.loaded = false;
|
||||
ChunkDataPacket.CACHE.invalidate(getIdentifier());
|
||||
UpdateLightPacket.CACHE.invalidate(getIdentifier());
|
||||
}
|
||||
}
|
@ -4,16 +4,24 @@ import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.pathfinding.PFBlock;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.network.packet.server.play.UpdateLightPacket;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.ArrayUtils;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -30,9 +38,10 @@ public class DynamicChunk extends Chunk {
|
||||
protected final Int2ObjectOpenHashMap<Block> entries = new Int2ObjectOpenHashMap<>();
|
||||
protected final Int2ObjectOpenHashMap<Block> tickableMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private long lastChangeTime;
|
||||
private volatile long lastChangeTime;
|
||||
|
||||
private SoftReference<ChunkDataPacket> cachedPacket = new SoftReference<>(null);
|
||||
private ByteBuffer cachedChunkBuffer;
|
||||
private ByteBuffer cachedLightBuffer;
|
||||
private long cachedPacketTime;
|
||||
|
||||
public DynamicChunk(@NotNull Instance instance, @Nullable Biome[] biomes, int chunkX, int chunkZ) {
|
||||
@ -117,23 +126,35 @@ public class DynamicChunk extends Chunk {
|
||||
return lastChangeTime;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ChunkDataPacket createChunkPacket() {
|
||||
ChunkDataPacket packet = cachedPacket.get();
|
||||
if (packet != null && cachedPacketTime == getLastChangeTime()) {
|
||||
return packet;
|
||||
public synchronized void sendChunk(@NotNull Player player) {
|
||||
if (!isLoaded()) return;
|
||||
final PlayerConnection connection = player.getPlayerConnection();
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
final long lastChange = getLastChangeTime();
|
||||
ByteBuffer chunkPacket = cachedChunkBuffer;
|
||||
ByteBuffer lightPacket = cachedLightBuffer;
|
||||
if (lastChange > cachedPacketTime || (chunkPacket == null || lightPacket == null)) {
|
||||
chunkPacket = PacketUtils.allocateTrimmedPacket(createChunkPacket());
|
||||
lightPacket = PacketUtils.allocateTrimmedPacket(createLightPacket());
|
||||
this.cachedChunkBuffer = chunkPacket;
|
||||
this.cachedLightBuffer = lightPacket;
|
||||
this.cachedPacketTime = lastChange;
|
||||
}
|
||||
PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
socketConnection.write(lightPacket);
|
||||
socketConnection.write(chunkPacket);
|
||||
} else {
|
||||
connection.sendPacket(createLightPacket());
|
||||
connection.sendPacket(createChunkPacket());
|
||||
}
|
||||
packet = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
|
||||
packet.biomes = biomes;
|
||||
packet.chunkX = chunkX;
|
||||
packet.chunkZ = chunkZ;
|
||||
packet.sections = sectionMap.clone(); // TODO deep clone
|
||||
packet.entries = entries.clone();
|
||||
}
|
||||
|
||||
this.cachedPacketTime = getLastChangeTime();
|
||||
this.cachedPacket = new SoftReference<>(packet);
|
||||
return packet;
|
||||
@Override
|
||||
public synchronized void sendChunk() {
|
||||
if (!isLoaded()) return;
|
||||
sendPacketToViewers(createLightPacket());
|
||||
sendPacketToViewers(createChunkPacket());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -153,6 +174,54 @@ public class DynamicChunk extends Chunk {
|
||||
this.entries.clear();
|
||||
}
|
||||
|
||||
private @NotNull ChunkDataPacket createChunkPacket() {
|
||||
ChunkDataPacket packet = new ChunkDataPacket();
|
||||
packet.biomes = biomes;
|
||||
packet.chunkX = chunkX;
|
||||
packet.chunkZ = chunkZ;
|
||||
packet.sections = sectionMap.clone(); // TODO deep clone
|
||||
packet.entries = entries.clone();
|
||||
return packet;
|
||||
}
|
||||
|
||||
private @NotNull UpdateLightPacket createLightPacket() {
|
||||
long skyMask = 0;
|
||||
long blockMask = 0;
|
||||
List<byte[]> skyLights = new ArrayList<>();
|
||||
List<byte[]> blockLights = new ArrayList<>();
|
||||
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
|
||||
updateLightPacket.chunkX = getChunkX();
|
||||
updateLightPacket.chunkZ = getChunkZ();
|
||||
|
||||
updateLightPacket.skyLight = skyLights;
|
||||
updateLightPacket.blockLight = blockLights;
|
||||
|
||||
final var sections = getSections();
|
||||
for (var entry : sections.entrySet()) {
|
||||
final int index = entry.getKey() + 1;
|
||||
final Section section = entry.getValue();
|
||||
|
||||
final var skyLight = section.getSkyLight();
|
||||
final var blockLight = section.getBlockLight();
|
||||
|
||||
if (!ArrayUtils.empty(skyLight)) {
|
||||
skyLights.add(skyLight);
|
||||
skyMask |= 1L << index;
|
||||
}
|
||||
if (!ArrayUtils.empty(blockLight)) {
|
||||
blockLights.add(blockLight);
|
||||
blockMask |= 1L << index;
|
||||
}
|
||||
}
|
||||
|
||||
updateLightPacket.skyLightMask = new long[]{skyMask};
|
||||
updateLightPacket.blockLightMask = new long[]{blockMask};
|
||||
updateLightPacket.emptySkyLightMask = new long[0];
|
||||
updateLightPacket.emptyBlockLightMask = new long[0];
|
||||
return updateLightPacket;
|
||||
}
|
||||
|
||||
private @Nullable Section getOptionalSection(int y) {
|
||||
final int sectionIndex = ChunkUtils.getSectionAt(y);
|
||||
return sectionMap.get(sectionIndex);
|
||||
|
@ -8,8 +8,6 @@ import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceContainer;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
@ -225,9 +223,8 @@ public class ChunkBatch implements Batch<ChunkCallback> {
|
||||
private void updateChunk(@NotNull Instance instance, Chunk chunk, IntSet updatedSections, @Nullable ChunkCallback callback, boolean safeCallback) {
|
||||
// Refresh chunk for viewers
|
||||
if (options.shouldSendUpdate()) {
|
||||
ChunkDataPacket chunkDataPacket = chunk.createChunkPacket();
|
||||
// TODO update all sections from `updatedSections`
|
||||
PacketUtils.sendGroupedPacket(chunk.getViewers(), chunkDataPacket);
|
||||
chunk.sendChunk();
|
||||
}
|
||||
|
||||
if (instance instanceof InstanceContainer) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.item;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.item.attribute.ItemAttribute;
|
||||
@ -13,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -36,7 +36,7 @@ public class ItemMeta implements TagReadable, Writeable {
|
||||
private final NBTCompound nbt;
|
||||
|
||||
private String cachedSNBT;
|
||||
private ByteBuf cachedBuffer;
|
||||
private ByteBuffer cachedBuffer;
|
||||
|
||||
protected ItemMeta(@NotNull ItemMetaBuilder metaBuilder) {
|
||||
this.damage = metaBuilder.damage;
|
||||
@ -154,6 +154,5 @@ public class ItemMeta implements TagReadable, Writeable {
|
||||
this.cachedBuffer = w.getBuffer();
|
||||
}
|
||||
writer.write(cachedBuffer);
|
||||
this.cachedBuffer.resetReaderIndex();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.network;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -16,7 +15,7 @@ import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.play.DisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.StringUtils;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
@ -24,6 +23,7 @@ import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@ -268,7 +268,7 @@ public final class ConnectionManager {
|
||||
* @param connection the player connection
|
||||
* @see PlayerConnection#disconnect() to properly disconnect a player
|
||||
*/
|
||||
public void removePlayer(@NotNull PlayerConnection connection) {
|
||||
public synchronized void removePlayer(@NotNull PlayerConnection connection) {
|
||||
final Player player = this.connectionPlayerMap.get(connection);
|
||||
if (player == null)
|
||||
return;
|
||||
@ -293,8 +293,8 @@ public final class ConnectionManager {
|
||||
EventDispatcher.call(asyncPlayerPreLoginEvent);
|
||||
// Close the player channel if he has been disconnected (kick)
|
||||
if (!player.isOnline()) {
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).getChannel().flush();
|
||||
if (playerConnection instanceof PlayerSocketConnection) {
|
||||
((PlayerSocketConnection) playerConnection).flush();
|
||||
}
|
||||
//playerConnection.disconnect();
|
||||
return;
|
||||
@ -312,8 +312,8 @@ public final class ConnectionManager {
|
||||
}
|
||||
// Send login success packet
|
||||
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(player.getUuid(), player.getUsername());
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).writeAndFlush(loginSuccessPacket);
|
||||
if (playerConnection instanceof PlayerSocketConnection) {
|
||||
((PlayerSocketConnection) playerConnection).writeAndFlush(loginSuccessPacket);
|
||||
} else {
|
||||
playerConnection.sendPacket(loginSuccessPacket);
|
||||
}
|
||||
@ -344,15 +344,18 @@ public final class ConnectionManager {
|
||||
/**
|
||||
* Shutdowns the connection manager by kicking all the currently connected players.
|
||||
*/
|
||||
public void shutdown() {
|
||||
public synchronized void shutdown() {
|
||||
DisconnectPacket disconnectPacket = new DisconnectPacket(shutdownText);
|
||||
for (Player player : getOnlinePlayers()) {
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
|
||||
final Channel channel = nettyPlayerConnection.getChannel();
|
||||
channel.writeAndFlush(disconnectPacket);
|
||||
channel.close();
|
||||
if (playerConnection instanceof PlayerSocketConnection) {
|
||||
final PlayerSocketConnection socketConnection = (PlayerSocketConnection) playerConnection;
|
||||
socketConnection.writeAndFlush(disconnectPacket);
|
||||
try {
|
||||
socketConnection.getChannel().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.players.clear();
|
||||
|
@ -1,27 +1,22 @@
|
||||
package net.minestom.server.network;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.client.handler.ClientLoginPacketsHandler;
|
||||
import net.minestom.server.network.packet.client.handler.ClientPlayPacketsHandler;
|
||||
import net.minestom.server.network.packet.client.handler.ClientStatusPacketsHandler;
|
||||
import net.minestom.server.network.packet.client.handshake.HandshakePacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.Readable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Responsible for processing client packets.
|
||||
@ -34,11 +29,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* the same meaning as it is a login or play packet).
|
||||
*/
|
||||
public final class PacketProcessor {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(PacketProcessor.class);
|
||||
|
||||
private final Map<ChannelHandlerContext, PlayerConnection> connectionPlayerConnectionMap = new ConcurrentHashMap<>();
|
||||
|
||||
// Protocols state
|
||||
private final ClientStatusPacketsHandler statusPacketsHandler;
|
||||
private final ClientLoginPacketsHandler loginPacketsHandler;
|
||||
@ -50,33 +42,13 @@ public final class PacketProcessor {
|
||||
this.playPacketsHandler = new ClientPlayPacketsHandler();
|
||||
}
|
||||
|
||||
public void process(@NotNull ChannelHandlerContext context, @NotNull InboundPacket packet) {
|
||||
final SocketChannel socketChannel = (SocketChannel) context.channel();
|
||||
|
||||
// Create the netty player connection object if not existing
|
||||
PlayerConnection playerConnection = connectionPlayerConnectionMap.get(context);
|
||||
if (playerConnection == null) {
|
||||
// Should never happen
|
||||
context.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the client from sending packets when disconnected (kick)
|
||||
if (!playerConnection.isOnline() || !socketChannel.isActive()) {
|
||||
playerConnection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment packet count (checked in PlayerConnection#update)
|
||||
public void process(@NotNull PlayerSocketConnection playerConnection, int packetId, ByteBuffer body) {
|
||||
if (MinecraftServer.getRateLimit() > 0) {
|
||||
// Increment packet count (checked in PlayerConnection#update)
|
||||
playerConnection.getPacketCounter().incrementAndGet();
|
||||
}
|
||||
|
||||
BinaryReader binaryReader = new BinaryReader(body);
|
||||
final ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
final int packetId = packet.getPacketId();
|
||||
BinaryReader binaryReader = new BinaryReader(packet.getBody());
|
||||
|
||||
if (connectionState == ConnectionState.UNKNOWN) {
|
||||
// Should be handshake packet
|
||||
if (packetId == 0) {
|
||||
@ -86,7 +58,6 @@ public final class PacketProcessor {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectionState) {
|
||||
case PLAY:
|
||||
final Player player = playerConnection.getPlayer();
|
||||
@ -108,34 +79,13 @@ public final class PacketProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a player connection from its channel.
|
||||
*
|
||||
* @param context the connection context
|
||||
* @return the connection of this channel, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public PlayerConnection getPlayerConnection(ChannelHandlerContext context) {
|
||||
return connectionPlayerConnectionMap.get(context);
|
||||
}
|
||||
|
||||
public void createPlayerConnection(@NotNull ChannelHandlerContext context) {
|
||||
final PlayerConnection playerConnection = new NettyPlayerConnection((SocketChannel) context.channel());
|
||||
connectionPlayerConnectionMap.put(context, playerConnection);
|
||||
}
|
||||
|
||||
public PlayerConnection removePlayerConnection(@NotNull ChannelHandlerContext context) {
|
||||
return connectionPlayerConnectionMap.remove(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler for client status packets.
|
||||
*
|
||||
* @return the status packets handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Status">Status packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientStatusPacketsHandler getStatusPacketsHandler() {
|
||||
public @NotNull ClientStatusPacketsHandler getStatusPacketsHandler() {
|
||||
return statusPacketsHandler;
|
||||
}
|
||||
|
||||
@ -145,8 +95,7 @@ public final class PacketProcessor {
|
||||
* @return the status login handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Login">Login packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientLoginPacketsHandler getLoginPacketsHandler() {
|
||||
public @NotNull ClientLoginPacketsHandler getLoginPacketsHandler() {
|
||||
return loginPacketsHandler;
|
||||
}
|
||||
|
||||
@ -156,8 +105,7 @@ public final class PacketProcessor {
|
||||
* @return the play packets handler
|
||||
* @see <a href="https://wiki.vg/Protocol#Play">Play packets</a>
|
||||
*/
|
||||
@NotNull
|
||||
public ClientPlayPacketsHandler getPlayPacketsHandler() {
|
||||
public @NotNull ClientPlayPacketsHandler getPlayPacketsHandler() {
|
||||
return playPacketsHandler;
|
||||
}
|
||||
|
||||
@ -170,20 +118,16 @@ public final class PacketProcessor {
|
||||
*/
|
||||
private void safeRead(@NotNull PlayerConnection connection, @NotNull Readable readable, @NotNull BinaryReader reader) {
|
||||
final int readableBytes = reader.available();
|
||||
|
||||
// Check if there is anything to read
|
||||
if (readableBytes == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
readable.read(reader);
|
||||
} catch (Exception e) {
|
||||
final Player player = connection.getPlayer();
|
||||
final String username = player != null ? player.getUsername() : "null";
|
||||
LOGGER.warn("Connection {} ({}) sent an unexpected packet.",
|
||||
connection.getRemoteAddress(),
|
||||
username);
|
||||
LOGGER.warn("Connection {} ({}) sent an unexpected packet.", connection.getRemoteAddress(), username);
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
|
@ -1,202 +0,0 @@
|
||||
package net.minestom.server.network.netty;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueue;
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.channel.ClientChannel;
|
||||
import net.minestom.server.network.netty.codec.*;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class NettyServer {
|
||||
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);
|
||||
public static final int BUFFER_SIZE = Integer.getInteger("minestom.channel-buffer-size", 65535);
|
||||
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
|
||||
1 << 21);
|
||||
|
||||
public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read
|
||||
|
||||
public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write
|
||||
public static final String DECRYPT_HANDLER_NAME = "decrypt"; // Read
|
||||
|
||||
public static final String GROUPED_PACKET_HANDLER_NAME = "grouped-packet"; // Write
|
||||
public static final String FRAMER_HANDLER_NAME = "framer"; // Read/write
|
||||
|
||||
public static final String COMPRESSOR_HANDLER_NAME = "compressor"; // Read/write
|
||||
|
||||
public static final String DECODER_HANDLER_NAME = "decoder"; // Read
|
||||
public static final String ENCODER_HANDLER_NAME = "encoder"; // Write
|
||||
public static final String CLIENT_CHANNEL_NAME = "handler"; // Read
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
private EventLoopGroup boss, worker;
|
||||
private ServerBootstrap bootstrap;
|
||||
|
||||
private ServerSocketChannel serverChannel;
|
||||
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public NettyServer(@NotNull PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the server by choosing which transport layer to use, number of threads, pipeline order, etc...
|
||||
* <p>
|
||||
* Called just before {@link #start(String, int)}.
|
||||
*/
|
||||
public void init() {
|
||||
Check.stateCondition(initialized, "Netty server has already been initialized!");
|
||||
this.initialized = true;
|
||||
|
||||
Class<? extends ServerChannel> channel;
|
||||
final int workerThreadCount = MinecraftServer.getNettyThreadCount();
|
||||
|
||||
// Find boss/worker event group
|
||||
{
|
||||
if (Epoll.isAvailable()) {
|
||||
boss = new EpollEventLoopGroup(2);
|
||||
worker = new EpollEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = EpollServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using epoll");
|
||||
} else if (KQueue.isAvailable()) {
|
||||
boss = new KQueueEventLoopGroup(2);
|
||||
worker = new KQueueEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = KQueueServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using kqueue");
|
||||
} else {
|
||||
boss = new NioEventLoopGroup(2);
|
||||
worker = new NioEventLoopGroup(workerThreadCount);
|
||||
|
||||
channel = NioServerSocketChannel.class;
|
||||
|
||||
LOGGER.info("Using NIO");
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap = new ServerBootstrap()
|
||||
.group(boss, worker)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.channel(channel);
|
||||
|
||||
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
protected void initChannel(@NotNull SocketChannel ch) {
|
||||
ChannelConfig config = ch.config();
|
||||
config.setOption(ChannelOption.TCP_NODELAY, true);
|
||||
config.setOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
config.setOption(ChannelOption.SO_SNDBUF, BUFFER_SIZE);
|
||||
config.setAllocator(ByteBufAllocator.DEFAULT);
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
// First check should verify if the packet is a legacy ping (from 1.6 version and earlier)
|
||||
// Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6)
|
||||
pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler());
|
||||
|
||||
// Used to bypass all the previous handlers by directly sending a framed buffer
|
||||
pipeline.addLast(GROUPED_PACKET_HANDLER_NAME, new GroupedPacketHandler());
|
||||
|
||||
// Adds packetLength at start | Reads framed buffer
|
||||
pipeline.addLast(FRAMER_HANDLER_NAME, new PacketFramer(packetProcessor));
|
||||
|
||||
// Reads buffer and create inbound packet
|
||||
pipeline.addLast(DECODER_HANDLER_NAME, new PacketDecoder());
|
||||
|
||||
// Writes packet to buffer
|
||||
pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder());
|
||||
|
||||
pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the address to start the server.
|
||||
*
|
||||
* @param address the server address
|
||||
* @param port the server port
|
||||
*/
|
||||
public void start(@NotNull String address, int port) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
|
||||
// Bind address
|
||||
try {
|
||||
ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync();
|
||||
|
||||
if (!cf.isSuccess()) {
|
||||
throw new IllegalStateException("Unable to bind server at " + address + ":" + port);
|
||||
}
|
||||
|
||||
this.serverChannel = (ServerSocketChannel) cf.channel();
|
||||
} catch (InterruptedException ex) {
|
||||
MinecraftServer.getExceptionManager().handleException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public ServerSocketChannel getServerChannel() {
|
||||
return serverChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the address of the server.
|
||||
*
|
||||
* @return the server address, null if the address isn't bound yet
|
||||
*/
|
||||
@Nullable
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port used by the server.
|
||||
*
|
||||
* @return the server port, 0 if the address isn't bound yet
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the server.
|
||||
*/
|
||||
public void stop() {
|
||||
try {
|
||||
this.boss.shutdownGracefully().sync();
|
||||
this.worker.shutdownGracefully().sync();
|
||||
this.serverChannel.closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package net.minestom.server.network.netty.channel;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(ClientChannel.class);
|
||||
|
||||
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public ClientChannel(@NotNull PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(@NotNull ChannelHandlerContext ctx) {
|
||||
//System.out.println("CONNECTION");
|
||||
packetProcessor.createPlayerConnection(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(ChannelHandlerContext ctx, InboundPacket packet) {
|
||||
try {
|
||||
packetProcessor.process(ctx, packet);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
} finally {
|
||||
// Check remaining
|
||||
final ByteBuf body = packet.getBody();
|
||||
final int packetId = packet.getPacketId();
|
||||
|
||||
final int availableBytes = body.readableBytes();
|
||||
|
||||
if (availableBytes > 0) {
|
||||
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||
|
||||
LOGGER.warn("WARNING: Packet 0x{} not fully read ({} bytes left), {}",
|
||||
Integer.toHexString(packetId),
|
||||
availableBytes,
|
||||
playerConnection);
|
||||
|
||||
body.skipBytes(availableBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(@NotNull ChannelHandlerContext ctx) {
|
||||
PlayerConnection playerConnection = packetProcessor.removePlayerConnection(ctx);
|
||||
if (playerConnection != null) {
|
||||
// Remove the connection
|
||||
playerConnection.refreshOnline(false);
|
||||
Player player = playerConnection.getPlayer();
|
||||
if (player != null) {
|
||||
player.remove();
|
||||
CONNECTION_MANAGER.removePlayer(playerConnection);
|
||||
}
|
||||
|
||||
// Release tick buffer
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
((NettyPlayerConnection) playerConnection).releaseTickBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (!ctx.channel().isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
MinecraftServer.getExceptionManager().handleException(cause);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
|
||||
public class GroupedPacketHandler extends MessageToByteEncoder<FramedPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FramedPacket msg, ByteBuf out) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, FramedPacket msg, boolean preferDirect) {
|
||||
return msg.getBody().retainedSlice();
|
||||
}
|
||||
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.server.ServerListPingEvent;
|
||||
import net.minestom.server.ping.ServerListPingType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class LegacyPingHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private ByteBuf buf;
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object object) {
|
||||
final ByteBuf buf = (ByteBuf) object;
|
||||
|
||||
if (this.buf != null) {
|
||||
try {
|
||||
handle1_6(ctx, buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buf.markReaderIndex();
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
try {
|
||||
if (buf.readUnsignedByte() == 0xFE) {
|
||||
int length = buf.readableBytes();
|
||||
|
||||
switch (length) {
|
||||
case 0:
|
||||
if (trySendResponse(ServerListPingType.LEGACY_UNVERSIONED, ctx)) return;
|
||||
break;
|
||||
case 1:
|
||||
if (buf.readUnsignedByte() != 1) return;
|
||||
|
||||
if (trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx)) return;
|
||||
break;
|
||||
default:
|
||||
if (buf.readUnsignedByte() != 0x01 || buf.readUnsignedByte() != 0xFA) return;
|
||||
|
||||
handle1_6(ctx, buf);
|
||||
break;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
flag = false;
|
||||
}
|
||||
} finally {
|
||||
if (flag) {
|
||||
buf.resetReaderIndex();
|
||||
ctx.channel().pipeline().remove("legacy-ping");
|
||||
ctx.fireChannelRead(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handle1_6(ChannelHandlerContext ctx, ByteBuf part) {
|
||||
ByteBuf buf = this.buf;
|
||||
|
||||
if (buf == null) {
|
||||
this.buf = buf = ctx.alloc().buffer();
|
||||
buf.markReaderIndex();
|
||||
} else {
|
||||
buf.resetReaderIndex();
|
||||
}
|
||||
|
||||
buf.writeBytes(part);
|
||||
|
||||
if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String s = readLegacyString(buf);
|
||||
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s.equals("MC|PingHost")) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int protocolVersion = buf.readByte();
|
||||
|
||||
if (readLegacyString(buf) == null) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.skipBytes(4); // port
|
||||
|
||||
if (buf.isReadable()) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
|
||||
this.buf = null;
|
||||
|
||||
trySendResponse(ServerListPingType.LEGACY_VERSIONED, ctx);
|
||||
}
|
||||
|
||||
private void removeHandler(ChannelHandlerContext ctx) {
|
||||
ByteBuf buf = this.buf;
|
||||
this.buf = null;
|
||||
|
||||
buf.resetReaderIndex();
|
||||
ctx.pipeline().remove(this);
|
||||
ctx.fireChannelRead(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
if (this.buf != null) {
|
||||
this.buf.release();
|
||||
this.buf = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a {@link ServerListPingEvent} and sends the response, if the event was not cancelled.
|
||||
*
|
||||
* @param version the version
|
||||
* @param ctx the context
|
||||
* @return {@code true} if the response was cancelled, {@code false} otherwise
|
||||
*/
|
||||
private static boolean trySendResponse(@NotNull ServerListPingType version, @NotNull ChannelHandlerContext ctx) {
|
||||
final ServerListPingEvent event = new ServerListPingEvent(version);
|
||||
EventDispatcher.call(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return true;
|
||||
} else {
|
||||
// get the response string
|
||||
String s = version.getPingResponse(event.getResponseData());
|
||||
|
||||
// create the buffer
|
||||
ByteBuf response = Unpooled.buffer();
|
||||
response.writeByte(255);
|
||||
|
||||
final char[] chars = s.toCharArray();
|
||||
|
||||
response.writeShort(chars.length);
|
||||
|
||||
for (char c : chars) {
|
||||
response.writeChar(c);
|
||||
}
|
||||
|
||||
// write the buffer
|
||||
ctx.pipeline().firstContext().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String readLegacyString(ByteBuf buf) {
|
||||
int size = buf.readShort() * Character.BYTES;
|
||||
if (!buf.isReadable(size)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE);
|
||||
buf.skipBytes(size);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (2020) [artem]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
private final static int MAX_SIZE = 2097152;
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final Deflater deflater = new Deflater();
|
||||
private final Inflater inflater = new Inflater();
|
||||
|
||||
public PacketCompressor(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
PacketUtils.compressBuffer(deflater, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.readableBytes() != 0) {
|
||||
final int claimedUncompressedSize = Utils.readVarInt(in);
|
||||
|
||||
if (claimedUncompressedSize == 0) {
|
||||
out.add(in.readRetainedSlice(in.readableBytes()));
|
||||
} else {
|
||||
if (claimedUncompressedSize < this.threshold) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold);
|
||||
}
|
||||
|
||||
if (claimedUncompressedSize > MAX_SIZE) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than protocol maximum of " + MAX_SIZE);
|
||||
}
|
||||
|
||||
// TODO optimize to do not initialize arrays each time
|
||||
|
||||
byte[] input = new byte[in.readableBytes()];
|
||||
in.readBytes(input);
|
||||
|
||||
inflater.setInput(input);
|
||||
byte[] output = new byte[claimedUncompressedSize];
|
||||
inflater.inflate(output);
|
||||
inflater.reset();
|
||||
|
||||
out.add(Unpooled.wrappedBuffer(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() > 0) {
|
||||
final int packetId = Utils.readVarInt(buf);
|
||||
list.add(new InboundPacket(packetId, buf));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
|
||||
public class PacketEncoder extends MessageToByteEncoder<ServerPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ServerPacket packet, ByteBuf buf) {
|
||||
PacketUtils.writePacket(buf, packet);
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
public final static Logger LOGGER = LoggerFactory.getLogger(PacketFramer.class);
|
||||
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public PacketFramer(PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
PacketUtils.frameBuffer(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) {
|
||||
buf.markReaderIndex();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!buf.isReadable()) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
final byte b = buf.readByte();
|
||||
|
||||
if (b >= 0) {
|
||||
buf.resetReaderIndex();
|
||||
|
||||
final int packetSize = Utils.readVarInt(buf);
|
||||
|
||||
// Max packet size check
|
||||
if (packetSize >= MinecraftServer.getMaxPacketSize()) {
|
||||
final PlayerConnection playerConnection = packetProcessor.getPlayerConnection(ctx);
|
||||
if (playerConnection != null) {
|
||||
final String identifier = playerConnection.getIdentifier();
|
||||
LOGGER.warn("An user ({}) sent a packet over the maximum size ({})",
|
||||
identifier, packetSize);
|
||||
} else {
|
||||
LOGGER.warn("An unregistered user sent a packet over the maximum size ({})", packetSize);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
if (buf.readableBytes() < packetSize) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(buf.readRetainedSlice(packetSize));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CorruptedFrameException("length wider than 21-bit");
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a packet which is already framed. (packet id+payload) + optional compression
|
||||
* Can be used if you want to send the exact same buffer to multiple clients without processing it more than once.
|
||||
*/
|
||||
public class FramedPacket {
|
||||
|
||||
private final ByteBuf body;
|
||||
|
||||
public FramedPacket(@NotNull ByteBuf body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class InboundPacket {
|
||||
|
||||
private final int packetId;
|
||||
private final ByteBuf body;
|
||||
|
||||
public InboundPacket(int id, @NotNull ByteBuf body) {
|
||||
this.packetId = id;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package net.minestom.server.network.packet;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a packet which is already framed. (packet id+payload) + optional compression
|
||||
* Can be used if you want to send the exact same buffer to multiple clients without processing it more than once.
|
||||
*/
|
||||
public final class FramedPacket {
|
||||
private final int packetId;
|
||||
private final ByteBuffer body;
|
||||
|
||||
public FramedPacket(int packetId, @NotNull ByteBuffer body) {
|
||||
this.packetId = packetId;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int packetId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public @NotNull ByteBuffer body() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -4,10 +4,8 @@ import net.minestom.server.network.packet.client.status.PingPacket;
|
||||
import net.minestom.server.network.packet.client.status.StatusRequestPacket;
|
||||
|
||||
public class ClientStatusPacketsHandler extends ClientPacketsHandler {
|
||||
|
||||
public ClientStatusPacketsHandler() {
|
||||
register(0x00, StatusRequestPacket::new);
|
||||
register(0x01, PingPacket::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import net.minestom.server.extras.bungee.BungeeCordProxy;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
@ -54,8 +54,8 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
// Bungee support (IP forwarding)
|
||||
if (BungeeCordProxy.isEnabled() && connection instanceof NettyPlayerConnection) {
|
||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
if (BungeeCordProxy.isEnabled() && connection instanceof PlayerSocketConnection) {
|
||||
PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
|
||||
if (serverAddress != null) {
|
||||
final String[] split = serverAddress.split("\00");
|
||||
@ -65,7 +65,7 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
|
||||
final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1],
|
||||
((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort());
|
||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||
socketConnection.setRemoteAddress(socketAddress);
|
||||
|
||||
UUID playerUuid = UUID.fromString(
|
||||
split[2]
|
||||
@ -79,11 +79,11 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
playerSkin = BungeeCordProxy.readSkin(split[3]);
|
||||
}
|
||||
|
||||
nettyPlayerConnection.UNSAFE_setBungeeUuid(playerUuid);
|
||||
nettyPlayerConnection.UNSAFE_setBungeeSkin(playerSkin);
|
||||
socketConnection.UNSAFE_setBungeeUuid(playerUuid);
|
||||
socketConnection.UNSAFE_setBungeeSkin(playerSkin);
|
||||
} else {
|
||||
nettyPlayerConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING));
|
||||
nettyPlayerConnection.disconnect();
|
||||
socketConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING));
|
||||
socketConnection.disconnect();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -92,9 +92,9 @@ public class HandshakePacket implements ClientPreplayPacket {
|
||||
}
|
||||
}
|
||||
|
||||
if (connection instanceof NettyPlayerConnection) {
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
// Give to the connection the server info that the client used
|
||||
((NettyPlayerConnection) connection).refreshServerInformation(serverAddress, serverPort, protocolVersion);
|
||||
((PlayerSocketConnection) connection).refreshServerInformation(serverAddress, serverPort, protocolVersion);
|
||||
}
|
||||
|
||||
switch (nextState) {
|
||||
|
@ -7,8 +7,8 @@ import net.minestom.server.data.type.array.ByteArrayData;
|
||||
import net.minestom.server.extras.MojangAuth;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
@ -35,31 +35,26 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
||||
|
||||
@Override
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
// Encryption is only support for netty connection
|
||||
if (!(connection instanceof NettyPlayerConnection)) {
|
||||
// Encryption is only support for socket connection
|
||||
if (!(connection instanceof PlayerSocketConnection)) {
|
||||
return;
|
||||
}
|
||||
final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
final PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
AsyncUtils.runAsync(() -> {
|
||||
try {
|
||||
final String loginUsername = nettyConnection.getLoginUsername();
|
||||
if (!Arrays.equals(nettyConnection.getNonce(), getNonce())) {
|
||||
final String loginUsername = socketConnection.getLoginUsername();
|
||||
if (!Arrays.equals(socketConnection.getNonce(), getNonce())) {
|
||||
MinecraftServer.LOGGER.error("{} tried to login with an invalid nonce!", loginUsername);
|
||||
return;
|
||||
}
|
||||
if (loginUsername != null && !loginUsername.isEmpty()) {
|
||||
|
||||
final byte[] digestedData = MojangCrypt.digestData("", MojangAuth.getKeyPair().getPublic(), getSecretKey());
|
||||
|
||||
if (digestedData == null) {
|
||||
// Incorrect key, probably because of the client
|
||||
MinecraftServer.LOGGER.error("Connection {} failed initializing encryption.", nettyConnection.getRemoteAddress());
|
||||
MinecraftServer.LOGGER.error("Connection {} failed initializing encryption.", socketConnection.getRemoteAddress());
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// Query Mojang's sessionserver.
|
||||
final String serverId = new BigInteger(digestedData).toString(16);
|
||||
InputStream gameProfileStream = new URL(
|
||||
@ -70,7 +65,7 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
||||
).openStream();
|
||||
|
||||
final JsonObject gameProfile = GSON.fromJson(new InputStreamReader(gameProfileStream), JsonObject.class);
|
||||
nettyConnection.setEncryptionKey(getSecretKey());
|
||||
socketConnection.setEncryptionKey(getSecretKey());
|
||||
UUID profileUUID = UUID.fromString(gameProfile.get("id").getAsString().replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"));
|
||||
String profileName = gameProfile.get("name").getAsString();
|
||||
|
||||
@ -85,8 +80,8 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
|
||||
|
||||
@Override
|
||||
public void read(@NotNull BinaryReader reader) {
|
||||
sharedSecret = ByteArrayData.decodeByteArray(reader);
|
||||
verifyToken = ByteArrayData.decodeByteArray(reader);
|
||||
this.sharedSecret = ByteArrayData.decodeByteArray(reader);
|
||||
this.verifyToken = ByteArrayData.decodeByteArray(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,7 +9,7 @@ import net.minestom.server.extras.velocity.VelocityProxy;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
@ -34,9 +34,9 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
// Proxy support
|
||||
if (connection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
final String channel = nettyPlayerConnection.getPluginRequestChannel(messageId);
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
final PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
final String channel = socketConnection.getPluginRequestChannel(messageId);
|
||||
|
||||
if (channel != null) {
|
||||
boolean success = false;
|
||||
@ -68,13 +68,13 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
||||
|
||||
if (success) {
|
||||
if (socketAddress != null) {
|
||||
nettyPlayerConnection.setRemoteAddress(socketAddress);
|
||||
socketConnection.setRemoteAddress(socketAddress);
|
||||
}
|
||||
if (playerUsername != null) {
|
||||
nettyPlayerConnection.UNSAFE_setLoginUsername(playerUsername);
|
||||
socketConnection.UNSAFE_setLoginUsername(playerUsername);
|
||||
}
|
||||
|
||||
final String username = nettyPlayerConnection.getLoginUsername();
|
||||
final String username = socketConnection.getLoginUsername();
|
||||
final UUID uuid = playerUuid != null ?
|
||||
playerUuid : CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||
|
||||
@ -82,7 +82,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
|
||||
player.setSkin(playerSkin);
|
||||
} else {
|
||||
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);
|
||||
nettyPlayerConnection.sendPacket(disconnectPacket);
|
||||
socketConnection.sendPacket(disconnectPacket);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.EncryptionRequestPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginPluginRequestPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -29,70 +29,58 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
|
||||
@Override
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
|
||||
final boolean isNettyClient = connection instanceof NettyPlayerConnection;
|
||||
|
||||
final boolean isSocketConnection = connection instanceof PlayerSocketConnection;
|
||||
// Cache the login username and start compression if enabled
|
||||
if (isNettyClient) {
|
||||
NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
nettyPlayerConnection.UNSAFE_setLoginUsername(username);
|
||||
if (isSocketConnection) {
|
||||
PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
socketConnection.UNSAFE_setLoginUsername(username);
|
||||
|
||||
// Compression
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
if (threshold > 0) {
|
||||
nettyPlayerConnection.startCompression();
|
||||
socketConnection.startCompression();
|
||||
}
|
||||
}
|
||||
// Proxy support (only for socket clients)
|
||||
if (isSocketConnection) {
|
||||
final PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
// Velocity support
|
||||
if (VelocityProxy.isEnabled()) {
|
||||
final int messageId = ThreadLocalRandom.current().nextInt();
|
||||
final String channel = VelocityProxy.PLAYER_INFO_CHANNEL;
|
||||
// Important in order to retrieve the channel in the response packet
|
||||
socketConnection.addPluginRequestEntry(messageId, channel);
|
||||
|
||||
LoginPluginRequestPacket loginPluginRequestPacket = new LoginPluginRequestPacket();
|
||||
loginPluginRequestPacket.messageId = messageId;
|
||||
loginPluginRequestPacket.channel = channel;
|
||||
loginPluginRequestPacket.data = null;
|
||||
connection.sendPacket(loginPluginRequestPacket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy support (only for netty clients)
|
||||
if (isNettyClient) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
{
|
||||
// Velocity support
|
||||
if (VelocityProxy.isEnabled()) {
|
||||
|
||||
final int messageId = ThreadLocalRandom.current().nextInt();
|
||||
final String channel = VelocityProxy.PLAYER_INFO_CHANNEL;
|
||||
|
||||
// Important in order to retrieve the channel in the response packet
|
||||
nettyPlayerConnection.addPluginRequestEntry(messageId, channel);
|
||||
|
||||
LoginPluginRequestPacket loginPluginRequestPacket = new LoginPluginRequestPacket();
|
||||
loginPluginRequestPacket.messageId = messageId;
|
||||
loginPluginRequestPacket.channel = channel;
|
||||
loginPluginRequestPacket.data = null;
|
||||
connection.sendPacket(loginPluginRequestPacket);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (MojangAuth.isEnabled() && isNettyClient) {
|
||||
if (MojangAuth.isEnabled() && isSocketConnection) {
|
||||
// Mojang auth
|
||||
if (CONNECTION_MANAGER.getPlayer(username) != null) {
|
||||
connection.sendPacket(new LoginDisconnectPacket(ALREADY_CONNECTED));
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection;
|
||||
|
||||
nettyPlayerConnection.setConnectionState(ConnectionState.LOGIN);
|
||||
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(nettyPlayerConnection);
|
||||
nettyPlayerConnection.sendPacket(encryptionRequestPacket);
|
||||
final PlayerSocketConnection socketConnection = (PlayerSocketConnection) connection;
|
||||
socketConnection.setConnectionState(ConnectionState.LOGIN);
|
||||
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(socketConnection);
|
||||
socketConnection.sendPacket(encryptionRequestPacket);
|
||||
} else {
|
||||
final boolean bungee = BungeeCordProxy.isEnabled();
|
||||
// Offline
|
||||
final UUID playerUuid = bungee && isNettyClient ?
|
||||
((NettyPlayerConnection) connection).getBungeeUuid() :
|
||||
final UUID playerUuid = bungee && isSocketConnection ?
|
||||
((PlayerSocketConnection) connection).getBungeeUuid() :
|
||||
CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
|
||||
|
||||
Player player = CONNECTION_MANAGER.startPlayState(connection, playerUuid, username, true);
|
||||
if (bungee && isNettyClient) {
|
||||
player.setSkin(((NettyPlayerConnection) connection).getBungeeSkin());
|
||||
if (bungee && isSocketConnection) {
|
||||
player.setSkin(((PlayerSocketConnection) connection).getBungeeSkin());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,7 +92,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
if(username.length() > 16)
|
||||
if (username.length() > 16)
|
||||
throw new IllegalArgumentException("Username is not allowed to be longer than 16 characters");
|
||||
writer.writeSizedString(username);
|
||||
}
|
||||
|
@ -16,12 +16,8 @@ public class StatusRequestPacket implements ClientPreplayPacket {
|
||||
public void process(@NotNull PlayerConnection connection) {
|
||||
final ServerListPingType pingVersion = ServerListPingType.fromModernProtocolVersion(connection.getProtocolVersion());
|
||||
final ServerListPingEvent statusRequestEvent = new ServerListPingEvent(connection, pingVersion);
|
||||
EventDispatcher.callCancellable(statusRequestEvent, () -> {
|
||||
final ResponsePacket responsePacket = new ResponsePacket();
|
||||
responsePacket.jsonResponse = pingVersion.getPingResponse(statusRequestEvent.getResponseData());
|
||||
|
||||
connection.sendPacket(responsePacket);
|
||||
});
|
||||
EventDispatcher.callCancellable(statusRequestEvent, () ->
|
||||
connection.sendPacket(new ResponsePacket(pingVersion.getPingResponse(statusRequestEvent.getResponseData()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,7 +7,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ResponsePacket implements ServerPacket {
|
||||
|
||||
public String jsonResponse = "";
|
||||
public String jsonResponse;
|
||||
|
||||
public ResponsePacket(String jsonResponse) {
|
||||
this.jsonResponse = jsonResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
|
@ -4,7 +4,7 @@ import net.minestom.server.data.type.array.ByteArrayData;
|
||||
import net.minestom.server.extras.MojangAuth;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -16,7 +16,7 @@ public class EncryptionRequestPacket implements ServerPacket {
|
||||
public byte[] publicKey;
|
||||
public byte[] nonce = new byte[4];
|
||||
|
||||
public EncryptionRequestPacket(NettyPlayerConnection connection) {
|
||||
public EncryptionRequestPacket(PlayerSocketConnection connection) {
|
||||
ThreadLocalRandom.current().nextBytes(nonce);
|
||||
connection.setNonce(nonce);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.network.packet.server.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.unimi.dsi.fastutil.ints.Int2LongRBTreeMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -15,22 +13,17 @@ import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES);
|
||||
public class ChunkDataPacket implements ServerPacket {
|
||||
|
||||
public Biome[] biomes;
|
||||
public int chunkX, chunkZ;
|
||||
@ -42,22 +35,13 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
private static final int MAX_BITS_PER_ENTRY = 16;
|
||||
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
|
||||
|
||||
// Cacheable data
|
||||
private final UUID identifier;
|
||||
private final long timestamp;
|
||||
/**
|
||||
* Heightmaps NBT, as read from raw packet data.
|
||||
* Only filled by #read, and unused at the moment.
|
||||
*/
|
||||
public NBTCompound heightmapsNBT;
|
||||
|
||||
private ChunkDataPacket() {
|
||||
this(new UUID(0, 0), 0);
|
||||
}
|
||||
|
||||
public ChunkDataPacket(@Nullable UUID identifier, long timestamp) {
|
||||
this.identifier = identifier;
|
||||
this.timestamp = timestamp;
|
||||
public ChunkDataPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +49,7 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
writer.writeInt(chunkX);
|
||||
writer.writeInt(chunkZ);
|
||||
|
||||
ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
|
||||
ByteBuffer blocks = ByteBuffer.allocate(MAX_BUFFER_SIZE);
|
||||
|
||||
Int2LongRBTreeMap maskMap = new Int2LongRBTreeMap();
|
||||
|
||||
@ -120,9 +104,8 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
}
|
||||
|
||||
// Data
|
||||
writer.writeVarInt(blocks.writerIndex());
|
||||
writer.writeVarInt(blocks.position());
|
||||
writer.write(blocks);
|
||||
blocks.release();
|
||||
|
||||
// Block entities
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
@ -244,19 +227,4 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.CHUNK_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
@ -4,19 +4,12 @@ import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
public static final TemporaryPacketCache CACHE = new TemporaryPacketCache(5, TimeUnit.MINUTES);
|
||||
public class UpdateLightPacket implements ServerPacket {
|
||||
|
||||
public int chunkX;
|
||||
public int chunkZ;
|
||||
@ -32,21 +25,10 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
public List<byte[]> skyLight = new ArrayList<>();
|
||||
public List<byte[]> blockLight = new ArrayList<>();
|
||||
|
||||
// Cacheable data
|
||||
private final UUID identifier;
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* Default constructor, required for reflection operations.
|
||||
* This one will make a packet that is not meant to be cached
|
||||
*/
|
||||
public UpdateLightPacket() {
|
||||
this(UUID.randomUUID(), Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public UpdateLightPacket(@Nullable UUID identifier, long timestamp) {
|
||||
this.identifier = identifier;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,19 +100,4 @@ public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.UPDATE_LIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
* It can be extended to create a new kind of player (NPC for instance).
|
||||
*/
|
||||
public abstract class PlayerConnection {
|
||||
|
||||
protected static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
||||
|
||||
private Player player;
|
||||
@ -63,8 +62,7 @@ public abstract class PlayerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AtomicInteger getPacketCounter() {
|
||||
public @NotNull AtomicInteger getPacketCounter() {
|
||||
return packetCounter;
|
||||
}
|
||||
|
||||
@ -74,8 +72,7 @@ public abstract class PlayerConnection {
|
||||
*
|
||||
* @return this connection identifier
|
||||
*/
|
||||
@NotNull
|
||||
public String getIdentifier() {
|
||||
public @NotNull String getIdentifier() {
|
||||
final Player player = getPlayer();
|
||||
return player != null ?
|
||||
player.getUsername() :
|
||||
@ -114,9 +111,7 @@ public abstract class PlayerConnection {
|
||||
*
|
||||
* @return the remote address
|
||||
*/
|
||||
@NotNull
|
||||
public abstract SocketAddress getRemoteAddress();
|
||||
|
||||
public abstract @NotNull SocketAddress getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Gets protocol version of client.
|
||||
@ -135,7 +130,7 @@ public abstract class PlayerConnection {
|
||||
* @return the server address used
|
||||
*/
|
||||
public @Nullable String getServerAddress() {
|
||||
return MinecraftServer.getNettyServer().getAddress();
|
||||
return MinecraftServer.getServer().getAddress();
|
||||
}
|
||||
|
||||
|
||||
@ -147,7 +142,7 @@ public abstract class PlayerConnection {
|
||||
* @return the server port used
|
||||
*/
|
||||
public int getServerPort() {
|
||||
return MinecraftServer.getNettyServer().getPort();
|
||||
return MinecraftServer.getServer().getPort();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,8 +155,7 @@ public abstract class PlayerConnection {
|
||||
*
|
||||
* @return the player, can be null if not initialized yet
|
||||
*/
|
||||
@Nullable
|
||||
public Player getPlayer() {
|
||||
public @Nullable Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@ -198,8 +192,7 @@ public abstract class PlayerConnection {
|
||||
*
|
||||
* @return the client connection state
|
||||
*/
|
||||
@NotNull
|
||||
public ConnectionState getConnectionState() {
|
||||
public @NotNull ConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
|
@ -1,53 +1,58 @@
|
||||
package net.minestom.server.network.player;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.adventure.MinestomAdventure;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.PlayerSkin;
|
||||
import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.network.netty.codec.PacketCompressor;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.utils.BufUtils;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.network.socket.Worker;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.binary.BinaryBuffer;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
* Represents a networking connection with Netty.
|
||||
* Represents a socket connection.
|
||||
* <p>
|
||||
* It is the implementation used for all network client.
|
||||
*/
|
||||
public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class PlayerSocketConnection extends PlayerConnection {
|
||||
private final Worker worker;
|
||||
private final SocketChannel channel;
|
||||
|
||||
private SocketAddress remoteAddress;
|
||||
|
||||
private boolean encrypted = false;
|
||||
private boolean compressed = false;
|
||||
private volatile boolean encrypted = false;
|
||||
private volatile boolean compressed = false;
|
||||
|
||||
//Could be null. Only used for Mojang Auth
|
||||
private byte[] nonce = new byte[4];
|
||||
private Cipher decryptCipher;
|
||||
private Cipher encryptCipher;
|
||||
|
||||
// Data from client packets
|
||||
private String loginUsername;
|
||||
@ -63,13 +68,95 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
private UUID bungeeUuid;
|
||||
private PlayerSkin bungeeSkin;
|
||||
|
||||
private final Object tickBufferLock = new Object();
|
||||
private volatile ByteBuf tickBuffer = BufUtils.direct();
|
||||
private final BinaryBuffer tickBuffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE);
|
||||
private volatile BinaryBuffer cacheBuffer;
|
||||
|
||||
public NettyPlayerConnection(@NotNull SocketChannel channel) {
|
||||
public PlayerSocketConnection(@NotNull Worker worker, @NotNull SocketChannel channel, SocketAddress remoteAddress) {
|
||||
super();
|
||||
this.worker = worker;
|
||||
this.channel = channel;
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
this.remoteAddress = remoteAddress;
|
||||
}
|
||||
|
||||
public void processPackets(Worker.Context workerContext, PacketProcessor packetProcessor) {
|
||||
final var readBuffer = workerContext.readBuffer;
|
||||
// Decrypt data
|
||||
if (encrypted) {
|
||||
final Cipher cipher = decryptCipher;
|
||||
final int remainingBytes = readBuffer.readableBytes();
|
||||
final byte[] bytes = readBuffer.readRemainingBytes();
|
||||
byte[] output = new byte[cipher.getOutputSize(remainingBytes)];
|
||||
try {
|
||||
cipher.update(bytes, 0, remainingBytes, output, 0);
|
||||
} catch (ShortBufferException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
return;
|
||||
}
|
||||
readBuffer.clear();
|
||||
readBuffer.writeBytes(output);
|
||||
}
|
||||
final int limit = readBuffer.writerOffset();
|
||||
// Read all packets
|
||||
while (readBuffer.readableBytes() > 0) {
|
||||
final var beginMark = readBuffer.mark();
|
||||
try {
|
||||
// Ensure that the buffer contains the full packet (or wait for next socket read)
|
||||
final int packetLength = readBuffer.readVarInt();
|
||||
final int packetEnd = readBuffer.readerOffset() + packetLength;
|
||||
if (packetEnd > readBuffer.writerOffset()) {
|
||||
// Integrity fail
|
||||
throw new BufferUnderflowException();
|
||||
}
|
||||
// Read packet https://wiki.vg/Protocol#Packet_format
|
||||
BinaryBuffer content;
|
||||
if (!compressed) {
|
||||
// Compression disabled, payload is following
|
||||
content = readBuffer;
|
||||
} else {
|
||||
final int dataLength = readBuffer.readVarInt();
|
||||
if (dataLength == 0) {
|
||||
// Data is too small to be compressed, payload is following
|
||||
content = readBuffer;
|
||||
} else {
|
||||
// Decompress to content buffer
|
||||
content = workerContext.contentBuffer;
|
||||
final var contentStartMark = content.mark();
|
||||
try {
|
||||
final var inflater = workerContext.inflater;
|
||||
inflater.setInput(readBuffer.asByteBuffer(readBuffer.readerOffset(), packetEnd));
|
||||
inflater.inflate(content.asByteBuffer(0, content.capacity()));
|
||||
inflater.reset();
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
content.reset(contentStartMark);
|
||||
}
|
||||
}
|
||||
// Process packet
|
||||
final int packetId = content.readVarInt();
|
||||
try {
|
||||
final ByteBuffer payload = content.asByteBuffer(content.readerOffset(), packetEnd);
|
||||
packetProcessor.process(this, packetId, payload);
|
||||
} catch (Exception e) {
|
||||
// Error while reading the packet
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
break;
|
||||
}
|
||||
// Position buffer to read the next packet
|
||||
readBuffer.reset(packetEnd, limit);
|
||||
} catch (BufferUnderflowException e) {
|
||||
readBuffer.reset(beginMark);
|
||||
this.cacheBuffer = BinaryBuffer.copy(readBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void consumeCache(BinaryBuffer buffer) {
|
||||
if (cacheBuffer != null) {
|
||||
buffer.write(cacheBuffer);
|
||||
this.cacheBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,11 +167,9 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*/
|
||||
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
||||
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
||||
this.decryptCipher = MojangCrypt.getCipher(2, secretKey);
|
||||
this.encryptCipher = MojangCrypt.getCipher(1, secretKey);
|
||||
this.encrypted = true;
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME,
|
||||
new Decrypter(MojangCrypt.getCipher(2, secretKey)));
|
||||
channel.pipeline().addBefore(NettyServer.GROUPED_PACKET_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME,
|
||||
new Encrypter(MojangCrypt.getCipher(1, secretKey)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,11 +181,8 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
Check.stateCondition(compressed, "Compression is already enabled!");
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
|
||||
|
||||
this.compressed = true;
|
||||
writeAndFlush(new SetCompressionPacket(threshold));
|
||||
channel.pipeline().addAfter(NettyServer.FRAMER_HANDLER_NAME, NettyServer.COMPRESSOR_HANDLER_NAME,
|
||||
new PacketCompressor(threshold));
|
||||
this.compressed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,21 +194,16 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*/
|
||||
@Override
|
||||
public void sendPacket(@NotNull ServerPacket serverPacket, boolean skipTranslating) {
|
||||
if (!channel.isActive())
|
||||
return;
|
||||
|
||||
if (!channel.isConnected()) return;
|
||||
if (shouldSendPacket(serverPacket)) {
|
||||
if (getPlayer() != null) {
|
||||
final Player player = getPlayer();
|
||||
if (player != null) {
|
||||
// Flush happen during #update()
|
||||
if (serverPacket instanceof CacheablePacket && MinecraftServer.hasPacketCaching()) {
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
CacheablePacket.writeCache(tickBuffer, serverPacket);
|
||||
}
|
||||
} else {
|
||||
write(serverPacket, skipTranslating);
|
||||
if ((MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && !skipTranslating) && serverPacket instanceof ComponentHoldingServerPacket) {
|
||||
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component ->
|
||||
GlobalTranslator.render(component, Objects.requireNonNullElseGet(player.getLocale(), MinestomAdventure::getDefaultLocale)));
|
||||
}
|
||||
write(serverPacket);
|
||||
} else {
|
||||
// Player is probably not logged yet
|
||||
writeAndFlush(serverPacket);
|
||||
@ -134,100 +211,61 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public void write(@NotNull Object message) {
|
||||
this.write(message, false);
|
||||
}
|
||||
|
||||
public void write(@NotNull Object message, boolean skipTranslating) {
|
||||
if (message instanceof FramedPacket) {
|
||||
final FramedPacket framedPacket = (FramedPacket) message;
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
final ByteBuf body = framedPacket.getBody();
|
||||
tickBuffer.writeBytes(body, body.readerIndex(), body.readableBytes());
|
||||
public void write(@NotNull ByteBuffer buffer) {
|
||||
synchronized (tickBuffer) {
|
||||
buffer.flip();
|
||||
if (!tickBuffer.canWrite(buffer.limit())) {
|
||||
// Tick buffer is full, flush before appending
|
||||
flush();
|
||||
}
|
||||
return;
|
||||
} else if (message instanceof ServerPacket) {
|
||||
ServerPacket serverPacket = (ServerPacket) message;
|
||||
|
||||
if ((MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && !skipTranslating) && getPlayer() != null && serverPacket instanceof ComponentHoldingServerPacket) {
|
||||
serverPacket = ((ComponentHoldingServerPacket) serverPacket).copyWithOperator(component ->
|
||||
GlobalTranslator.render(component, Objects.requireNonNullElseGet(getPlayer().getLocale(), MinestomAdventure::getDefaultLocale)));
|
||||
}
|
||||
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
PacketUtils.writeFramedPacket(tickBuffer, serverPacket);
|
||||
}
|
||||
return;
|
||||
} else if (message instanceof ByteBuf) {
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
tickBuffer.writeBytes((ByteBuf) message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new UnsupportedOperationException("type " + message.getClass() + " is not supported");
|
||||
}
|
||||
|
||||
public void writeAndFlush(@NotNull Object message) {
|
||||
writeWaitingPackets();
|
||||
ChannelFuture channelFuture = channel.writeAndFlush(message);
|
||||
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
channelFuture.addListener(future -> {
|
||||
if (!future.isSuccess() && channel.isActive()) {
|
||||
MinecraftServer.getExceptionManager().handleException(future.cause());
|
||||
}
|
||||
});
|
||||
this.tickBuffer.write(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeWaitingPackets() {
|
||||
if (tickBuffer.writerIndex() == 0) {
|
||||
// Nothing to write
|
||||
return;
|
||||
}
|
||||
public void write(@NotNull FramedPacket framedPacket) {
|
||||
write(framedPacket.body());
|
||||
}
|
||||
|
||||
// Retrieve safe copy
|
||||
final ByteBuf copy;
|
||||
synchronized (tickBufferLock) {
|
||||
if (tickBuffer.refCnt() == 0)
|
||||
return;
|
||||
copy = tickBuffer;
|
||||
tickBuffer = tickBuffer.alloc().buffer(tickBuffer.writerIndex());
|
||||
}
|
||||
public void write(@NotNull ServerPacket packet) {
|
||||
// TODO write directly to the tick buffer
|
||||
write(PacketUtils.createFramedPacket(packet, compressed));
|
||||
}
|
||||
|
||||
// Write copied buffer to netty
|
||||
ChannelFuture channelFuture = channel.write(new FramedPacket(copy));
|
||||
channelFuture.addListener(future -> copy.release());
|
||||
|
||||
// Netty debug
|
||||
if (MinecraftServer.shouldProcessNettyErrors()) {
|
||||
channelFuture.addListener(future -> {
|
||||
if (!future.isSuccess() && channel.isActive()) {
|
||||
MinecraftServer.getExceptionManager().handleException(future.cause());
|
||||
}
|
||||
});
|
||||
public void writeAndFlush(@NotNull ServerPacket packet) {
|
||||
synchronized (tickBuffer) {
|
||||
write(packet);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
final int bufferSize = tickBuffer.writerIndex();
|
||||
if (bufferSize > 0) {
|
||||
if (channel.isActive()) {
|
||||
writeWaitingPackets();
|
||||
channel.flush();
|
||||
if (!channel.isOpen()) return;
|
||||
synchronized (tickBuffer) {
|
||||
if (tickBuffer.readableBytes() == 0) return;
|
||||
try {
|
||||
if (encrypted) {
|
||||
final Cipher cipher = encryptCipher;
|
||||
// Encrypt data first
|
||||
final int remainingBytes = tickBuffer.readableBytes();
|
||||
final byte[] bytes = tickBuffer.readRemainingBytes();
|
||||
byte[] outTempArray = new byte[cipher.getOutputSize(remainingBytes)];
|
||||
cipher.update(bytes, 0, remainingBytes, outTempArray);
|
||||
this.tickBuffer.clear();
|
||||
this.tickBuffer.writeBytes(outTempArray);
|
||||
}
|
||||
this.tickBuffer.writeChannel(channel);
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
} catch (ShortBufferException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
this.tickBuffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
public @NotNull SocketAddress getRemoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
@ -238,19 +276,17 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*
|
||||
* @param remoteAddress the new connection remote address
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public void setRemoteAddress(@NotNull SocketAddress remoteAddress) {
|
||||
this.remoteAddress = remoteAddress;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
refreshOnline(false);
|
||||
this.channel.close();
|
||||
this.worker.disconnect(this, channel);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Channel getChannel() {
|
||||
public @NotNull SocketChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@ -261,8 +297,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
*
|
||||
* @return the username given by the client, unchecked
|
||||
*/
|
||||
@Nullable
|
||||
public String getLoginUsername() {
|
||||
public @Nullable String getLoginUsername() {
|
||||
return loginUsername;
|
||||
}
|
||||
|
||||
@ -309,7 +344,6 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields.
|
||||
*
|
||||
@ -323,9 +357,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public UUID getBungeeUuid() {
|
||||
public @Nullable UUID getBungeeUuid() {
|
||||
return bungeeUuid;
|
||||
}
|
||||
|
||||
@ -333,8 +365,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
this.bungeeUuid = bungeeUuid;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PlayerSkin getBungeeSkin() {
|
||||
public @Nullable PlayerSkin getBungeeSkin() {
|
||||
return bungeeSkin;
|
||||
}
|
||||
|
||||
@ -367,8 +398,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
* @param messageId the message id
|
||||
* @return the channel linked to the message id, null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public String getPluginRequestChannel(int messageId) {
|
||||
public @Nullable String getPluginRequestChannel(int messageId) {
|
||||
return pluginRequestMap.get(messageId);
|
||||
}
|
||||
|
||||
@ -381,12 +411,6 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseTickBuffer() {
|
||||
synchronized (tickBufferLock) {
|
||||
tickBuffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getNonce() {
|
||||
return nonce;
|
||||
}
|
94
src/main/java/net/minestom/server/network/socket/Server.java
Normal file
94
src/main/java/net/minestom/server/network/socket/Server.java
Normal file
@ -0,0 +1,94 @@
|
||||
package net.minestom.server.network.socket;
|
||||
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class Server {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(Server.class);
|
||||
public static final int WORKER_COUNT = Integer.getInteger("minestom.workers",
|
||||
Runtime.getRuntime().availableProcessors() * 2);
|
||||
public static final int SOCKET_BUFFER_SIZE = Integer.getInteger("minestom.buffer-size", 262_143);
|
||||
public static final int MAX_PACKET_SIZE = 2_097_151; // 3 bytes var-int
|
||||
public static final boolean NO_DELAY = true;
|
||||
|
||||
private volatile boolean stop;
|
||||
|
||||
private final Selector selector = Selector.open();
|
||||
private final List<Worker> workers = new ArrayList<>(WORKER_COUNT);
|
||||
private int index;
|
||||
|
||||
private ServerSocketChannel serverSocket;
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public Server(PacketProcessor packetProcessor) throws IOException {
|
||||
// Create all workers
|
||||
for (int i = 0; i < WORKER_COUNT; i++) {
|
||||
Worker worker = new Worker(this, packetProcessor);
|
||||
this.workers.add(worker);
|
||||
worker.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void start(SocketAddress address) throws IOException {
|
||||
this.serverSocket = ServerSocketChannel.open();
|
||||
serverSocket.bind(address);
|
||||
serverSocket.configureBlocking(false);
|
||||
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
|
||||
serverSocket.socket().setReceiveBufferSize(SOCKET_BUFFER_SIZE);
|
||||
LOGGER.info("Server starting, wait for connections");
|
||||
new Thread(() -> {
|
||||
while (!stop) {
|
||||
// Busy wait for connections
|
||||
try {
|
||||
this.selector.select(key -> {
|
||||
if (!key.isAcceptable()) return;
|
||||
try {
|
||||
// Register socket and forward to thread
|
||||
Worker worker = findWorker();
|
||||
final SocketChannel client = serverSocket.accept();
|
||||
worker.receiveConnection(client);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}, "Ms-entrypoint").start();
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return !stop;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.stop = true;
|
||||
this.selector.wakeup();
|
||||
this.workers.forEach(worker -> worker.selector.wakeup());
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
private Worker findWorker() {
|
||||
this.index = ++index % WORKER_COUNT;
|
||||
return workers.get(index);
|
||||
}
|
||||
}
|
103
src/main/java/net/minestom/server/network/socket/Worker.java
Normal file
103
src/main/java/net/minestom/server/network/socket/Worker.java
Normal file
@ -0,0 +1,103 @@
|
||||
package net.minestom.server.network.socket;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.utils.binary.BinaryBuffer;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class Worker extends Thread {
|
||||
private static final AtomicInteger COUNTER = new AtomicInteger();
|
||||
|
||||
final Selector selector = Selector.open();
|
||||
private final Context context = new Context();
|
||||
private final Map<SocketChannel, PlayerSocketConnection> connectionMap = new ConcurrentHashMap<>();
|
||||
private final Server server;
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public Worker(Server server, PacketProcessor packetProcessor) throws IOException {
|
||||
super(null, null, "Ms-worker-" + COUNTER.getAndIncrement());
|
||||
this.server = server;
|
||||
this.packetProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (server.isOpen()) {
|
||||
try {
|
||||
this.selector.select(key -> {
|
||||
final SocketChannel channel = (SocketChannel) key.channel();
|
||||
if (!channel.isOpen()) return;
|
||||
if (!key.isReadable()) return;
|
||||
var connection = connectionMap.get(channel);
|
||||
try {
|
||||
var readBuffer = context.readBuffer;
|
||||
// Consume last incomplete packet
|
||||
connection.consumeCache(readBuffer);
|
||||
// Read & process
|
||||
readBuffer.readChannel(channel);
|
||||
connection.processPackets(context, packetProcessor);
|
||||
} catch (IOException e) {
|
||||
// TODO print exception? (should ignore disconnection)
|
||||
connection.disconnect();
|
||||
} finally {
|
||||
context.clearBuffers();
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect(PlayerSocketConnection connection, SocketChannel channel) {
|
||||
try {
|
||||
channel.close();
|
||||
this.connectionMap.remove(channel);
|
||||
MinecraftServer.getConnectionManager().removePlayer(connection);
|
||||
connection.refreshOnline(false);
|
||||
Player player = connection.getPlayer();
|
||||
if (player != null) {
|
||||
player.remove();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void receiveConnection(SocketChannel channel) throws IOException {
|
||||
this.connectionMap.put(channel, new PlayerSocketConnection(this, channel, channel.getRemoteAddress()));
|
||||
channel.configureBlocking(false);
|
||||
channel.register(selector, SelectionKey.OP_READ);
|
||||
var socket = channel.socket();
|
||||
socket.setSendBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setReceiveBufferSize(Server.SOCKET_BUFFER_SIZE);
|
||||
socket.setTcpNoDelay(Server.NO_DELAY);
|
||||
this.selector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains objects that we can be shared across all the connection of a {@link Worker worker}.
|
||||
*/
|
||||
public static final class Context {
|
||||
public final BinaryBuffer readBuffer = BinaryBuffer.ofSize(Server.SOCKET_BUFFER_SIZE);
|
||||
public final BinaryBuffer contentBuffer = BinaryBuffer.ofSize(Server.MAX_PACKET_SIZE);
|
||||
public final Inflater inflater = new Inflater();
|
||||
|
||||
void clearBuffers() {
|
||||
this.readBuffer.clear();
|
||||
this.contentBuffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -75,13 +75,13 @@ public enum ServerListPingType {
|
||||
* @see OpenToLAN
|
||||
*/
|
||||
public static @NotNull String getOpenToLANPing(@NotNull ResponseData data) {
|
||||
return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getNettyServer().getPort());
|
||||
return String.format(LAN_PING_FORMAT, SECTION.serialize(data.getDescription()), MinecraftServer.getServer().getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a legacy ping response for client versions below the Netty rewrite (1.6-).
|
||||
*
|
||||
* @param data the response data
|
||||
* @param data the response data
|
||||
* @param supportsVersions if the client supports recieving the versions of the server
|
||||
* @return the response
|
||||
*/
|
||||
@ -99,7 +99,7 @@ public enum ServerListPingType {
|
||||
/**
|
||||
* Creates a modern ping response for client versions above the Netty rewrite (1.7+).
|
||||
*
|
||||
* @param data the response data
|
||||
* @param data the response data
|
||||
* @param supportsFullRgb if the client supports full RGB
|
||||
* @return the response
|
||||
*/
|
||||
|
@ -1,13 +0,0 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
|
||||
public class BufUtils {
|
||||
|
||||
private static final PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
|
||||
|
||||
public static ByteBuf direct() {
|
||||
return alloc.ioBuffer();
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.audience.ForwardingAudience;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -8,28 +7,29 @@ import net.minestom.server.adventure.MinestomAdventure;
|
||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.network.socket.Server;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.callback.validator.PlayerValidator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/**
|
||||
* Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuf}
|
||||
* Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuffer}
|
||||
* for network processing.
|
||||
*/
|
||||
public final class PacketUtils {
|
||||
|
||||
private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
||||
private static final ThreadLocal<Deflater> COMPRESSOR = ThreadLocal.withInitial(Deflater::new);
|
||||
private static final ThreadLocal<ByteBuffer> BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(Server.SOCKET_BUFFER_SIZE));
|
||||
|
||||
private PacketUtils() {
|
||||
}
|
||||
@ -77,50 +77,37 @@ public final class PacketUtils {
|
||||
* @param playerValidator optional callback to check if a specify player of {@code players} should receive the packet
|
||||
*/
|
||||
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet,
|
||||
@Nullable PlayerValidator playerValidator) {
|
||||
@NotNull PlayerValidator playerValidator) {
|
||||
if (players.isEmpty())
|
||||
return;
|
||||
|
||||
// work out if the packet needs to be sent individually due to server-side translating
|
||||
boolean needsTranslating = false;
|
||||
if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION && packet instanceof ComponentHoldingServerPacket) {
|
||||
needsTranslating = ComponentUtils.areAnyTranslatable(((ComponentHoldingServerPacket) packet).components());
|
||||
}
|
||||
|
||||
if (MinecraftServer.hasGroupedPacket() && !needsTranslating) {
|
||||
// Send grouped packet...
|
||||
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
|
||||
if (success) {
|
||||
final ByteBuf finalBuffer = createFramedPacket(packet);
|
||||
final FramedPacket framedPacket = new FramedPacket(finalBuffer);
|
||||
|
||||
// Send packet to all players
|
||||
for (Player player : players) {
|
||||
if (!player.isOnline())
|
||||
continue;
|
||||
// Verify if the player should receive the packet
|
||||
if (playerValidator != null && !playerValidator.isValid(player))
|
||||
continue;
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
|
||||
nettyPlayerConnection.write(framedPacket, true);
|
||||
} else {
|
||||
playerConnection.sendPacket(packet);
|
||||
}
|
||||
if (!PACKET_LISTENER_MANAGER.processServerPacket(packet, players))
|
||||
return;
|
||||
final ByteBuffer finalBuffer = createFramedPacket(packet);
|
||||
final FramedPacket framedPacket = new FramedPacket(packet.getId(), finalBuffer);
|
||||
// Send packet to all players
|
||||
for (Player player : players) {
|
||||
if (!player.isOnline() || !playerValidator.isValid(player))
|
||||
continue;
|
||||
final PlayerConnection connection = player.getPlayerConnection();
|
||||
if (connection instanceof PlayerSocketConnection) {
|
||||
((PlayerSocketConnection) connection).write(framedPacket);
|
||||
} else {
|
||||
connection.sendPacket(packet);
|
||||
}
|
||||
finalBuffer.release(); // Release last reference
|
||||
}
|
||||
} else {
|
||||
// Write the same packet for each individual players
|
||||
for (Player player : players) {
|
||||
// Verify if the player should receive the packet
|
||||
if (playerValidator != null && !playerValidator.isValid(player))
|
||||
if (!player.isOnline() || !playerValidator.isValid(player))
|
||||
continue;
|
||||
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
playerConnection.sendPacket(packet, false);
|
||||
player.getPlayerConnection().sendPacket(packet, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,136 +119,78 @@ public final class PacketUtils {
|
||||
* @see #sendGroupedPacket(Collection, ServerPacket, PlayerValidator)
|
||||
*/
|
||||
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet) {
|
||||
sendGroupedPacket(players, packet, null);
|
||||
sendGroupedPacket(players, packet, player -> true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link ServerPacket} into a {@link ByteBuf}.
|
||||
*
|
||||
* @param buf the recipient of {@code packet}
|
||||
* @param packet the packet to write into {@code buf}
|
||||
*/
|
||||
public static void writePacket(@NotNull ByteBuf buf, @NotNull ServerPacket packet) {
|
||||
Utils.writeVarInt(buf, packet.getId());
|
||||
writePacketPayload(buf, packet);
|
||||
public static void writeFramedPacket(@NotNull ByteBuffer buffer,
|
||||
@NotNull ServerPacket packet,
|
||||
boolean compression) {
|
||||
if (!compression) {
|
||||
// Length + payload
|
||||
final int lengthIndex = Utils.writeEmptyVarIntHeader(buffer);
|
||||
Utils.writeVarInt(buffer, packet.getId());
|
||||
packet.write(new BinaryWriter(buffer));
|
||||
final int finalSize = buffer.position() - (lengthIndex + 3);
|
||||
Utils.writeVarIntHeader(buffer, lengthIndex, finalSize);
|
||||
return;
|
||||
}
|
||||
// Compressed format
|
||||
final int compressedIndex = Utils.writeEmptyVarIntHeader(buffer);
|
||||
final int uncompressedIndex = Utils.writeEmptyVarIntHeader(buffer);
|
||||
final int contentStart = buffer.position();
|
||||
|
||||
Utils.writeVarInt(buffer, packet.getId());
|
||||
packet.write(new BinaryWriter(buffer));
|
||||
final int packetSize = buffer.position() - contentStart;
|
||||
if (packetSize >= MinecraftServer.getCompressionThreshold()) {
|
||||
// Packet large enough, compress
|
||||
final int limitCache = buffer.limit();
|
||||
buffer.position(contentStart).limit(contentStart + packetSize);
|
||||
var uncompressedCopy = ByteBuffer.allocate(packetSize).put(buffer).flip();
|
||||
buffer.position(contentStart).limit(limitCache);
|
||||
|
||||
var deflater = COMPRESSOR.get();
|
||||
deflater.setInput(uncompressedCopy);
|
||||
deflater.finish();
|
||||
deflater.deflate(buffer);
|
||||
deflater.reset();
|
||||
|
||||
Utils.writeVarIntHeader(buffer, compressedIndex, (buffer.position() - contentStart) + 3);
|
||||
Utils.writeVarIntHeader(buffer, uncompressedIndex, packetSize);
|
||||
} else {
|
||||
Utils.writeVarIntHeader(buffer, compressedIndex, packetSize + 3);
|
||||
Utils.writeVarIntHeader(buffer, uncompressedIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param packet the packet to write
|
||||
*/
|
||||
private static void writePacketPayload(@NotNull ByteBuf buffer, @NotNull ServerPacket packet) {
|
||||
BinaryWriter writer = new BinaryWriter(buffer);
|
||||
public static ByteBuffer createFramedPacket(@NotNull ByteBuffer initial, @NotNull ServerPacket packet,
|
||||
boolean compression) {
|
||||
var buffer = initial;
|
||||
try {
|
||||
packet.write(writer);
|
||||
} catch (Exception e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
writeFramedPacket(buffer, packet, compression);
|
||||
} catch (BufferOverflowException e) {
|
||||
// In the unlikely case where the packet is bigger than the default buffer size,
|
||||
// increase to the highest authorized buffer size using heap (for cheap allocation)
|
||||
buffer = ByteBuffer.allocate(Server.MAX_PACKET_SIZE);
|
||||
writeFramedPacket(buffer, packet, compression);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a buffer for it to be understood by a Minecraft client.
|
||||
* <p>
|
||||
* The content of {@code packetBuffer} can be either a compressed or uncompressed packet buffer,
|
||||
* it depends of it the client did receive a {@link net.minestom.server.network.packet.server.login.SetCompressionPacket} packet before.
|
||||
*
|
||||
* @param packetBuffer the buffer containing compressed or uncompressed packet data
|
||||
* @param frameTarget the buffer which will receive the framed version of {@code from}
|
||||
*/
|
||||
public static void frameBuffer(@NotNull ByteBuf packetBuffer, @NotNull ByteBuf frameTarget) {
|
||||
final int packetSize = packetBuffer.readableBytes();
|
||||
final int headerSize = Utils.getVarIntSize(packetSize);
|
||||
if (headerSize > 3) {
|
||||
throw new IllegalStateException("Unable to fit " + headerSize + " into 3");
|
||||
}
|
||||
|
||||
frameTarget.ensureWritable(packetSize + headerSize);
|
||||
|
||||
Utils.writeVarInt(frameTarget, packetSize);
|
||||
frameTarget.writeBytes(packetBuffer, packetBuffer.readerIndex(), packetSize);
|
||||
public static ByteBuffer createFramedPacket(@NotNull ByteBuffer initial, @NotNull ServerPacket packet) {
|
||||
return createFramedPacket(initial, packet, MinecraftServer.getCompressionThreshold() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress using zlib the content of a packet.
|
||||
* <p>
|
||||
* {@code packetBuffer} needs to be the packet content without any header (if you want to use it to write a Minecraft packet).
|
||||
*
|
||||
* @param deflater the deflater for zlib compression
|
||||
* @param packetBuffer the buffer containing all the packet fields
|
||||
* @param compressionTarget the buffer which will receive the compressed version of {@code packetBuffer}
|
||||
*/
|
||||
public static void compressBuffer(@NotNull Deflater deflater, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) {
|
||||
final int packetLength = packetBuffer.readableBytes();
|
||||
final boolean compression = packetLength > MinecraftServer.getCompressionThreshold();
|
||||
Utils.writeVarInt(compressionTarget, compression ? packetLength : 0);
|
||||
if (compression) {
|
||||
compress(deflater, packetBuffer, compressionTarget);
|
||||
} else {
|
||||
compressionTarget.writeBytes(packetBuffer);
|
||||
}
|
||||
public static ByteBuffer createFramedPacket(@NotNull ServerPacket packet) {
|
||||
return createFramedPacket(BUFFER.get().clear(), packet);
|
||||
}
|
||||
|
||||
private static void compress(@NotNull Deflater deflater, @NotNull ByteBuf uncompressed, @NotNull ByteBuf compressed) {
|
||||
deflater.setInput(uncompressed.nioBuffer());
|
||||
deflater.finish();
|
||||
|
||||
while (!deflater.finished()) {
|
||||
ByteBuffer nioBuffer = compressed.nioBuffer(compressed.writerIndex(), compressed.writableBytes());
|
||||
compressed.writerIndex(deflater.deflate(nioBuffer) + compressed.writerIndex());
|
||||
|
||||
if (compressed.writableBytes() == 0) {
|
||||
compressed.ensureWritable(8192);
|
||||
}
|
||||
}
|
||||
|
||||
deflater.reset();
|
||||
public static ByteBuffer createFramedPacket(@NotNull ServerPacket packet, boolean compression) {
|
||||
return createFramedPacket(BUFFER.get().clear(), packet, compression);
|
||||
}
|
||||
|
||||
public static void writeFramedPacket(@NotNull ByteBuf buffer,
|
||||
@NotNull ServerPacket serverPacket) {
|
||||
final int compressionThreshold = MinecraftServer.getCompressionThreshold();
|
||||
|
||||
// Index of the var-int containing the complete packet length
|
||||
final int packetLengthIndex = Utils.writeEmpty3BytesVarInt(buffer);
|
||||
final int startIndex = buffer.writerIndex(); // Index where the content starts (after length)
|
||||
if (compressionThreshold > 0) {
|
||||
// Index of the uncompressed payload length
|
||||
final int dataLengthIndex = Utils.writeEmpty3BytesVarInt(buffer);
|
||||
|
||||
// Write packet
|
||||
final int contentIndex = buffer.writerIndex();
|
||||
writePacket(buffer, serverPacket);
|
||||
final int packetSize = buffer.writerIndex() - contentIndex;
|
||||
|
||||
final int uncompressedLength = packetSize >= compressionThreshold ? packetSize : 0;
|
||||
Utils.write3BytesVarInt(buffer, dataLengthIndex, uncompressedLength);
|
||||
if (uncompressedLength > 0) {
|
||||
// Packet large enough, compress
|
||||
ByteBuf uncompressedCopy = buffer.copy(contentIndex, packetSize);
|
||||
buffer.writerIndex(contentIndex);
|
||||
compress(COMPRESSOR.get(), uncompressedCopy, buffer);
|
||||
uncompressedCopy.release();
|
||||
}
|
||||
} else {
|
||||
// No compression, write packet id + payload
|
||||
writePacket(buffer, serverPacket);
|
||||
}
|
||||
// Total length
|
||||
final int totalPacketLength = buffer.writerIndex() - startIndex;
|
||||
Utils.write3BytesVarInt(buffer, packetLengthIndex, totalPacketLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "framed packet" (packet which can be send and understood by a Minecraft client)
|
||||
* from a server packet, directly into an output buffer.
|
||||
* <p>
|
||||
* Can be used if you want to store a raw buffer and send it later without the additional writing cost.
|
||||
* Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0.
|
||||
*/
|
||||
public static @NotNull ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
|
||||
ByteBuf packetBuf = BufUtils.direct();
|
||||
writeFramedPacket(packetBuf, serverPacket);
|
||||
return packetBuf;
|
||||
public static ByteBuffer allocateTrimmedPacket(@NotNull ServerPacket packet) {
|
||||
final var temp = PacketUtils.createFramedPacket(packet);
|
||||
return ByteBuffer.allocateDirect(temp.position()).put(temp.flip());
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap;
|
||||
import net.minestom.server.instance.palette.Palette;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class Utils {
|
||||
|
||||
private Utils() {
|
||||
@ -21,43 +22,46 @@ public final class Utils {
|
||||
? 4 : 5;
|
||||
}
|
||||
|
||||
public static void writeVarInt(@NotNull ByteBuf buf, int value) {
|
||||
// Took from velocity
|
||||
public static void writeVarInt(ByteBuffer buf, int value) {
|
||||
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||
buf.writeByte(value);
|
||||
buf.put((byte) value);
|
||||
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
|
||||
buf.writeShort(w);
|
||||
buf.putShort((short) ((value & 0x7F | 0x80) << 8 | (value >>> 7)));
|
||||
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
|
||||
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
buf.writeMedium(w);
|
||||
buf.put((byte) (value & 0x7F | 0x80));
|
||||
buf.put((byte) ((value >>> 7) & 0x7F | 0x80));
|
||||
buf.put((byte) (value >>> 14));
|
||||
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
|
||||
buf.putInt((value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21));
|
||||
} else {
|
||||
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
|
||||
buf.writeInt(w);
|
||||
buf.writeByte(value >>> 28);
|
||||
buf.putInt((value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80));
|
||||
buf.put((byte) (value >>> 28));
|
||||
}
|
||||
}
|
||||
|
||||
public static void write3BytesVarInt(@NotNull ByteBuf buffer, int startIndex, int value) {
|
||||
final int indexCache = buffer.writerIndex();
|
||||
buffer.writerIndex(startIndex);
|
||||
final int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||
buffer.writeMedium(w);
|
||||
buffer.writerIndex(indexCache);
|
||||
public static void writeVarIntHeader(@NotNull ByteBuffer buffer, int startIndex, int value) {
|
||||
final int indexCache = buffer.position();
|
||||
buffer.position(startIndex);
|
||||
buffer.put((byte) (value & 0x7F | 0x80));
|
||||
buffer.put((byte) ((value >>> 7) & 0x7F | 0x80));
|
||||
buffer.put((byte) (value >>> 14));
|
||||
buffer.position(indexCache);
|
||||
}
|
||||
|
||||
public static int writeEmpty3BytesVarInt(@NotNull ByteBuf buffer) {
|
||||
final int index = buffer.writerIndex();
|
||||
buffer.writeMedium(0);
|
||||
public static int writeEmptyVarIntHeader(@NotNull ByteBuffer buffer) {
|
||||
final int index = buffer.position();
|
||||
buffer.putShort((short) 0);
|
||||
buffer.put((byte) 0);
|
||||
return index;
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
public static int readVarInt(ByteBuffer buf) {
|
||||
int i = 0;
|
||||
final int maxRead = Math.min(5, buf.readableBytes());
|
||||
final int maxRead = Math.min(5, buf.remaining());
|
||||
for (int j = 0; j < maxRead; j++) {
|
||||
final int k = buf.readByte();
|
||||
final int k = buf.get();
|
||||
i |= (k & 0x7F) << j * 7;
|
||||
if ((k & 0x80) != 128) {
|
||||
return i;
|
||||
@ -66,12 +70,12 @@ public final class Utils {
|
||||
throw new RuntimeException("VarInt is too big");
|
||||
}
|
||||
|
||||
public static long readVarLong(@NotNull ByteBuf buffer) {
|
||||
public static long readVarLong(@NotNull ByteBuffer buffer) {
|
||||
int numRead = 0;
|
||||
long result = 0;
|
||||
byte read;
|
||||
do {
|
||||
read = buffer.readByte();
|
||||
read = buffer.get();
|
||||
long value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
@ -84,14 +88,14 @@ public final class Utils {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void writeVarLong(BinaryWriter writer, long value) {
|
||||
public static void writeVarLong(ByteBuffer buffer, long value) {
|
||||
do {
|
||||
byte temp = (byte) (value & 0b01111111);
|
||||
value >>>= 7;
|
||||
if (value != 0) {
|
||||
temp |= 0b10000000;
|
||||
}
|
||||
writer.writeByte(temp);
|
||||
buffer.put(temp);
|
||||
} while (value != 0);
|
||||
}
|
||||
|
||||
@ -117,13 +121,12 @@ public final class Utils {
|
||||
return new UUID(uuidMost, uuidLeast);
|
||||
}
|
||||
|
||||
public static void writePaletteBlocks(ByteBuf buffer, Palette palette) {
|
||||
|
||||
public static void writePaletteBlocks(ByteBuffer buffer, Palette palette) {
|
||||
final short blockCount = palette.getBlockCount();
|
||||
final int bitsPerEntry = palette.getBitsPerEntry();
|
||||
|
||||
buffer.writeShort(blockCount);
|
||||
buffer.writeByte((byte) bitsPerEntry);
|
||||
buffer.putShort(blockCount);
|
||||
buffer.put((byte) bitsPerEntry);
|
||||
|
||||
// Palette
|
||||
if (bitsPerEntry < 9) {
|
||||
@ -138,7 +141,7 @@ public final class Utils {
|
||||
final long[] blocks = palette.getBlocks();
|
||||
writeVarInt(buffer, blocks.length);
|
||||
for (long datum : blocks) {
|
||||
buffer.writeLong(datum);
|
||||
buffer.putLong(datum);
|
||||
}
|
||||
}
|
||||
|
||||
|
177
src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java
Normal file
177
src/main/java/net/minestom/server/utils/binary/BinaryBuffer.java
Normal file
@ -0,0 +1,177 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTReader;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
/**
|
||||
* Manages off-heap memory.
|
||||
* Not thread-safe.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public final class BinaryBuffer {
|
||||
private ByteBuffer nioBuffer; // To become a `MemorySegment` once released
|
||||
private NBTReader nbtReader;
|
||||
private NBTWriter nbtWriter;
|
||||
|
||||
private final int capacity;
|
||||
private int readerOffset, writerOffset;
|
||||
|
||||
private BinaryBuffer(ByteBuffer buffer) {
|
||||
this.nioBuffer = buffer;
|
||||
this.capacity = buffer.capacity();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static BinaryBuffer ofSize(int size) {
|
||||
return new BinaryBuffer(ByteBuffer.allocateDirect(size));
|
||||
}
|
||||
|
||||
public static BinaryBuffer copy(BinaryBuffer buffer) {
|
||||
final int size = buffer.readableBytes();
|
||||
final var temp = ByteBuffer.allocateDirect(size)
|
||||
.put(buffer.asByteBuffer(0, size));
|
||||
return new BinaryBuffer(temp);
|
||||
}
|
||||
|
||||
public void write(ByteBuffer buffer) {
|
||||
final int size = buffer.remaining();
|
||||
// TODO jdk 13 put with index
|
||||
this.nioBuffer.position(writerOffset).put(buffer);
|
||||
this.writerOffset += size;
|
||||
}
|
||||
|
||||
public void write(BinaryBuffer buffer) {
|
||||
write(buffer.asByteBuffer(buffer.readerOffset, buffer.writerOffset));
|
||||
}
|
||||
|
||||
public int readVarInt() {
|
||||
int value = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final int offset = readerOffset + i;
|
||||
final byte k = nioBuffer.get(offset);
|
||||
value |= (k & 0x7F) << i * 7;
|
||||
if ((k & 0x80) != 128) {
|
||||
this.readerOffset = offset + 1;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("VarInt is too big");
|
||||
}
|
||||
|
||||
public @NotNull Marker mark() {
|
||||
return new Marker(readerOffset, writerOffset);
|
||||
}
|
||||
|
||||
public void reset(int readerOffset, int writerOffset) {
|
||||
this.readerOffset = readerOffset;
|
||||
this.writerOffset = writerOffset;
|
||||
}
|
||||
|
||||
public void reset(@NotNull Marker marker) {
|
||||
reset(marker.readerOffset(), marker.writerOffset());
|
||||
}
|
||||
|
||||
public boolean canWrite(int size) {
|
||||
return writerOffset + size <= capacity;
|
||||
}
|
||||
|
||||
public int capacity() {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
public int readerOffset() {
|
||||
return readerOffset;
|
||||
}
|
||||
|
||||
public int writerOffset() {
|
||||
return writerOffset;
|
||||
}
|
||||
|
||||
public int readableBytes() {
|
||||
return writerOffset - readerOffset;
|
||||
}
|
||||
|
||||
public void writeBytes(byte[] bytes) {
|
||||
this.nioBuffer.position(writerOffset).put(bytes);
|
||||
this.writerOffset += bytes.length;
|
||||
}
|
||||
|
||||
public byte[] readBytes(int length) {
|
||||
byte[] bytes = new byte[length];
|
||||
this.nioBuffer.position(readerOffset).get(bytes, 0, length);
|
||||
this.readerOffset += length;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public byte[] readRemainingBytes() {
|
||||
return readBytes(readableBytes());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.readerOffset = 0;
|
||||
this.writerOffset = 0;
|
||||
}
|
||||
|
||||
public ByteBuffer asByteBuffer(int reader, int writer) {
|
||||
return nioBuffer.position(reader).slice().limit(writer);
|
||||
}
|
||||
|
||||
public void writeChannel(WritableByteChannel channel) throws IOException {
|
||||
final int count = channel.write(asByteBuffer(readerOffset, writerOffset));
|
||||
if (count == -1) {
|
||||
// EOS
|
||||
throw new IOException("Disconnected");
|
||||
}
|
||||
this.readerOffset += count;
|
||||
}
|
||||
|
||||
public void readChannel(ReadableByteChannel channel) throws IOException {
|
||||
final int count = channel.read(asByteBuffer(readerOffset, capacity));
|
||||
if (count == -1) {
|
||||
// EOS
|
||||
throw new IOException("Disconnected");
|
||||
}
|
||||
this.writerOffset += count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BinaryBuffer{" +
|
||||
"readerOffset=" + readerOffset +
|
||||
", writerOffset=" + writerOffset +
|
||||
", capacity=" + capacity +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final class Marker {
|
||||
private final int readerOffset, writerOffset;
|
||||
|
||||
private Marker(int readerOffset, int writerOffset) {
|
||||
this.readerOffset = readerOffset;
|
||||
this.writerOffset = writerOffset;
|
||||
}
|
||||
|
||||
public int readerOffset() {
|
||||
return readerOffset;
|
||||
}
|
||||
|
||||
public int writerOffset() {
|
||||
return writerOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Marker{" +
|
||||
"readerOffset=" + readerOffset +
|
||||
", writerOffset=" + writerOffset +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
@ -17,6 +15,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTReader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
@ -27,16 +26,15 @@ import java.util.function.Supplier;
|
||||
* WARNING: not thread-safe.
|
||||
*/
|
||||
public class BinaryReader extends InputStream {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final ByteBuffer buffer;
|
||||
private final NBTReader nbtReader = new NBTReader(this, false);
|
||||
|
||||
public BinaryReader(@NotNull ByteBuf buffer) {
|
||||
public BinaryReader(@NotNull ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public BinaryReader(byte[] bytes) {
|
||||
this(Unpooled.wrappedBuffer(bytes));
|
||||
this(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
public int readVarInt() {
|
||||
@ -48,49 +46,49 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public boolean readBoolean() {
|
||||
return buffer.readBoolean();
|
||||
return buffer.get() == 1;
|
||||
}
|
||||
|
||||
public byte readByte() {
|
||||
return buffer.readByte();
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
public short readShort() {
|
||||
return buffer.readShort();
|
||||
return buffer.getShort();
|
||||
}
|
||||
|
||||
public char readChar() {
|
||||
return buffer.readChar();
|
||||
return buffer.getChar();
|
||||
}
|
||||
|
||||
public int readUnsignedShort() {
|
||||
return buffer.readUnsignedShort();
|
||||
return buffer.getShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as readInt
|
||||
*/
|
||||
public int readInteger() {
|
||||
return buffer.readInt();
|
||||
return buffer.getInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as readInteger, created for parity with BinaryWriter
|
||||
*/
|
||||
public int readInt() {
|
||||
return buffer.readInt();
|
||||
return buffer.getInt();
|
||||
}
|
||||
|
||||
public long readLong() {
|
||||
return buffer.readLong();
|
||||
return buffer.getLong();
|
||||
}
|
||||
|
||||
public float readFloat() {
|
||||
return buffer.readFloat();
|
||||
return buffer.getFloat();
|
||||
}
|
||||
|
||||
public double readDouble() {
|
||||
return buffer.readDouble();
|
||||
return buffer.getDouble();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,12 +103,9 @@ public class BinaryReader extends InputStream {
|
||||
*/
|
||||
public String readSizedString(int maxLength) {
|
||||
final int length = readVarInt();
|
||||
Check.stateCondition(!buffer.isReadable(length),
|
||||
"Trying to read a string that is too long (wanted {0}, only have {1})",
|
||||
length,
|
||||
buffer.readableBytes());
|
||||
final String str = buffer.toString(buffer.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buffer.skipBytes(length);
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
final String str = new String(bytes, StandardCharsets.UTF_8);
|
||||
Check.stateCondition(str.length() > maxLength,
|
||||
"String length ({0}) was higher than the max length of {1}", length, maxLength);
|
||||
return str;
|
||||
@ -121,10 +116,8 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public byte[] readBytes(int length) {
|
||||
ByteBuf buf = buffer.readBytes(length);
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
buf.release();
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@ -164,14 +157,11 @@ public class BinaryReader extends InputStream {
|
||||
}
|
||||
|
||||
public Point readBlockPosition() {
|
||||
final long value = buffer.readLong();
|
||||
return SerializerUtils.longToBlockPosition(value);
|
||||
return SerializerUtils.longToBlockPosition(buffer.getLong());
|
||||
}
|
||||
|
||||
public UUID readUuid() {
|
||||
final long most = readLong();
|
||||
final long least = readLong();
|
||||
return new UUID(most, least);
|
||||
return new UUID(readLong(), readLong());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,7 +215,7 @@ public class BinaryReader extends InputStream {
|
||||
return (T[]) result;
|
||||
}
|
||||
|
||||
public ByteBuf getBuffer() {
|
||||
public ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -236,7 +226,7 @@ public class BinaryReader extends InputStream {
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return buffer.readableBytes();
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
public NBT readTag() throws IOException, NBTException {
|
||||
@ -251,11 +241,12 @@ public class BinaryReader extends InputStream {
|
||||
* @param extractor the extraction code, simply call the reader's read* methods here.
|
||||
*/
|
||||
public byte[] extractBytes(Runnable extractor) {
|
||||
int startingPosition = getBuffer().readerIndex();
|
||||
int startingPosition = buffer.position();
|
||||
extractor.run();
|
||||
int endingPosition = getBuffer().readerIndex();
|
||||
int endingPosition = getBuffer().position();
|
||||
byte[] output = new byte[endingPosition - startingPosition];
|
||||
getBuffer().getBytes(startingPosition, output);
|
||||
buffer.get(output, 0, output.length);
|
||||
//buffer.get(startingPosition, output);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -16,8 +13,11 @@ import org.jglrxavpok.hephaistos.nbt.NBTWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@ -26,8 +26,7 @@ import java.util.function.Consumer;
|
||||
* WARNING: not thread-safe.
|
||||
*/
|
||||
public class BinaryWriter extends OutputStream {
|
||||
|
||||
private ByteBuf buffer;
|
||||
private ByteBuffer buffer;
|
||||
private NBTWriter nbtWriter; // Lazily initialized
|
||||
|
||||
/**
|
||||
@ -36,7 +35,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param initialCapacity the initial capacity of the binary writer
|
||||
*/
|
||||
public BinaryWriter(int initialCapacity) {
|
||||
this.buffer = Unpooled.buffer(initialCapacity);
|
||||
this.buffer = ByteBuffer.allocate(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,24 +43,15 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param buffer the writer buffer
|
||||
*/
|
||||
public BinaryWriter(@NotNull ByteBuf buffer) {
|
||||
public BinaryWriter(@NotNull ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BinaryWriter} from multiple buffers.
|
||||
*
|
||||
* @param buffers the buffers making this
|
||||
*/
|
||||
public BinaryWriter(@NotNull ByteBuf... buffers) {
|
||||
this.buffer = Unpooled.wrappedBuffer(buffers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BinaryWriter} with a "reasonably small initial capacity".
|
||||
*/
|
||||
public BinaryWriter() {
|
||||
this.buffer = Unpooled.buffer();
|
||||
this(500); // TODO prevent OOB
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +69,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param b the boolean to write
|
||||
*/
|
||||
public void writeBoolean(boolean b) {
|
||||
buffer.writeBoolean(b);
|
||||
buffer.put((byte) (b ? 1 : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +78,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param b the byte to write
|
||||
*/
|
||||
public void writeByte(byte b) {
|
||||
buffer.writeByte(b);
|
||||
buffer.put(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +87,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param c the char to write
|
||||
*/
|
||||
public void writeChar(char c) {
|
||||
buffer.writeChar(c);
|
||||
buffer.putChar(c);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +96,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param s the short to write
|
||||
*/
|
||||
public void writeShort(short s) {
|
||||
buffer.writeShort(s);
|
||||
buffer.putShort(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,7 +105,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param i the int to write
|
||||
*/
|
||||
public void writeInt(int i) {
|
||||
buffer.writeInt(i);
|
||||
buffer.putInt(i);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +114,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param l the long to write
|
||||
*/
|
||||
public void writeLong(long l) {
|
||||
buffer.writeLong(l);
|
||||
buffer.putLong(l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +123,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param f the float to write
|
||||
*/
|
||||
public void writeFloat(float f) {
|
||||
buffer.writeFloat(f);
|
||||
buffer.putFloat(f);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,7 +132,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param d the double to write
|
||||
*/
|
||||
public void writeDouble(double d) {
|
||||
buffer.writeDouble(d);
|
||||
buffer.putDouble(d);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,7 +150,7 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param l the long to write
|
||||
*/
|
||||
public void writeVarLong(long l) {
|
||||
Utils.writeVarLong(this, l);
|
||||
Utils.writeVarLong(buffer, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,9 +161,9 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param string the string to write
|
||||
*/
|
||||
public void writeSizedString(@NotNull String string) {
|
||||
final int utf8Bytes = ByteBufUtil.utf8Bytes(string);
|
||||
writeVarInt(utf8Bytes);
|
||||
buffer.writeCharSequence(string, StandardCharsets.UTF_8);
|
||||
final var bytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(bytes.length);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +174,8 @@ public class BinaryWriter extends OutputStream {
|
||||
* @param charset the charset to encode in
|
||||
*/
|
||||
public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
|
||||
buffer.writeCharSequence(string + '\0', charset);
|
||||
final var bytes = (string + '\0').getBytes(charset);
|
||||
writeBytes(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,8 +214,8 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param bytes the byte array to write
|
||||
*/
|
||||
public void writeBytes(@NotNull byte[] bytes) {
|
||||
buffer.writeBytes(bytes);
|
||||
public void writeBytes(byte @NotNull [] bytes) {
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -296,12 +287,12 @@ public class BinaryWriter extends OutputStream {
|
||||
writeable.write(this);
|
||||
}
|
||||
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
this.buffer.writeBytes(writer.getBuffer());
|
||||
public void write(@NotNull ByteBuffer buffer) {
|
||||
this.buffer.put(buffer.flip());
|
||||
}
|
||||
|
||||
public void write(@NotNull ByteBuf buffer) {
|
||||
this.buffer.writeBytes(buffer);
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
write(writer.buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,46 +314,52 @@ public class BinaryWriter extends OutputStream {
|
||||
* @return the byte array containing all the {@link BinaryWriter} data
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
byte[] bytes = new byte[buffer.readableBytes()];
|
||||
final int readerIndex = buffer.readerIndex();
|
||||
buffer.getBytes(readerIndex, bytes);
|
||||
buffer.flip();
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuf} at the beginning of this writer.
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the beginning of this writer.
|
||||
*
|
||||
* @param headerWriter the {@link BinaryWriter} to add at the beginning
|
||||
*/
|
||||
public void writeAtStart(@NotNull BinaryWriter headerWriter) {
|
||||
// Get the buffer of the header
|
||||
final ByteBuf headerBuf = headerWriter.getBuffer();
|
||||
final var headerBuf = headerWriter.getBuffer();
|
||||
// Merge both the headerBuf and this buffer
|
||||
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(headerBuf, buffer);
|
||||
final var finalBuffer = concat(headerBuf, buffer);
|
||||
// Change the buffer used by this writer
|
||||
setBuffer(finalBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuf} at the end of this writer.
|
||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the end of this writer.
|
||||
*
|
||||
* @param footerWriter the {@link BinaryWriter} to add at the end
|
||||
*/
|
||||
public void writeAtEnd(@NotNull BinaryWriter footerWriter) {
|
||||
// Get the buffer of the footer
|
||||
final ByteBuf footerBuf = footerWriter.getBuffer();
|
||||
final var footerBuf = footerWriter.getBuffer();
|
||||
// Merge both this buffer and the footerBuf
|
||||
final ByteBuf finalBuffer = Unpooled.wrappedBuffer(buffer, footerBuf);
|
||||
final var finalBuffer = concat(buffer, footerBuf);
|
||||
// Change the buffer used by this writer
|
||||
setBuffer(finalBuffer);
|
||||
}
|
||||
|
||||
public static ByteBuffer concat(final ByteBuffer... buffers) {
|
||||
final ByteBuffer combined = ByteBuffer.allocate(Arrays.stream(buffers).mapToInt(Buffer::remaining).sum());
|
||||
Arrays.stream(buffers).forEach(b -> combined.put(b.duplicate()));
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the raw buffer used by this binary writer.
|
||||
*
|
||||
* @return the raw buffer
|
||||
*/
|
||||
public @NotNull ByteBuf getBuffer() {
|
||||
public @NotNull ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@ -371,7 +368,7 @@ public class BinaryWriter extends OutputStream {
|
||||
*
|
||||
* @param buffer the new buffer used by this binary writer
|
||||
*/
|
||||
public void setBuffer(ByteBuf buffer) {
|
||||
public void setBuffer(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@ -381,7 +378,8 @@ public class BinaryWriter extends OutputStream {
|
||||
}
|
||||
|
||||
public void writeUnsignedShort(int yourShort) {
|
||||
buffer.writeShort(yourShort & 0xFFFF);
|
||||
// FIXME unsigned
|
||||
buffer.putShort((short) (yourShort & 0xFFFF));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,9 +0,0 @@
|
||||
package net.minestom.server.utils.binary;
|
||||
|
||||
public final class BitmaskUtil {
|
||||
|
||||
public static byte changeBit(byte value, byte mask, byte replacement, byte shift) {
|
||||
return (byte) (value & ~mask | (replacement << shift));
|
||||
}
|
||||
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implemented by {@link ServerPacket server packets} which can be temporary cached in memory to be re-sent later
|
||||
* without having to go through all the writing and compression.
|
||||
* <p>
|
||||
* {@link #getIdentifier()} is to differentiate this packet from the others of the same type.
|
||||
*/
|
||||
public interface CacheablePacket {
|
||||
|
||||
/**
|
||||
* Gets the cache linked to this packet.
|
||||
* <p>
|
||||
* WARNING: the cache needs to be shared between all the object instances, tips is to make it static.
|
||||
*
|
||||
* @return the temporary packet cache
|
||||
*/
|
||||
@NotNull TemporaryPacketCache getCache();
|
||||
|
||||
/**
|
||||
* Gets the identifier of this packet.
|
||||
* <p>
|
||||
* Used to verify if this packet is already cached or not.
|
||||
*
|
||||
* @return this packet identifier, null to prevent caching
|
||||
*/
|
||||
@Nullable UUID getIdentifier();
|
||||
|
||||
/**
|
||||
* Gets the last time this packet changed.
|
||||
*
|
||||
* @return the last packet update time in milliseconds
|
||||
*/
|
||||
long getTimestamp();
|
||||
|
||||
static @Nullable FramedPacket getCache(@NotNull ServerPacket serverPacket) {
|
||||
if (!(serverPacket instanceof CacheablePacket))
|
||||
return null;
|
||||
|
||||
final CacheablePacket cacheablePacket = (CacheablePacket) serverPacket;
|
||||
final UUID identifier = cacheablePacket.getIdentifier();
|
||||
if (identifier == null) {
|
||||
// This packet explicitly asks to do not retrieve the cache
|
||||
return null;
|
||||
} else {
|
||||
final long timestamp = cacheablePacket.getTimestamp();
|
||||
// Try to retrieve the cached buffer
|
||||
TemporaryCache<TimedBuffer> temporaryCache = cacheablePacket.getCache();
|
||||
TimedBuffer timedBuffer = temporaryCache.retrieve(identifier);
|
||||
|
||||
// Update the buffer if non-existent or outdated
|
||||
final boolean shouldUpdate = timedBuffer == null ||
|
||||
timestamp > timedBuffer.getTimestamp();
|
||||
|
||||
if (shouldUpdate) {
|
||||
// Buffer freed by guava cache #removalListener
|
||||
final ByteBuf buffer = PacketUtils.createFramedPacket(serverPacket);
|
||||
timedBuffer = new TimedBuffer(buffer, timestamp);
|
||||
temporaryCache.cache(identifier, timedBuffer);
|
||||
}
|
||||
|
||||
return new FramedPacket(timedBuffer.getBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
static void writeCache(@NotNull ByteBuf buffer, @NotNull ServerPacket serverPacket) {
|
||||
FramedPacket framedPacket = CacheablePacket.getCache(serverPacket);
|
||||
if (framedPacket == null) {
|
||||
PacketUtils.writeFramedPacket(buffer, serverPacket);
|
||||
return;
|
||||
}
|
||||
final ByteBuf body = framedPacket.getBody();
|
||||
synchronized (body) {
|
||||
if (framedPacket.getBody().refCnt() != 0) {
|
||||
buffer.writeBytes(body, body.readerIndex(), body.readableBytes());
|
||||
} else {
|
||||
PacketUtils.writeFramedPacket(buffer, serverPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Cache objects with a timeout.
|
||||
*
|
||||
* @param <T> the object type to cache
|
||||
*/
|
||||
public class TemporaryCache<T> {
|
||||
|
||||
private final Cache<UUID, T> cache;
|
||||
|
||||
/**
|
||||
* Creates a new temporary cache.
|
||||
*
|
||||
* @param duration the time before considering an object unused
|
||||
*/
|
||||
public TemporaryCache(long duration, TimeUnit timeUnit, RemovalListener<UUID, T> removalListener) {
|
||||
this.cache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(duration, timeUnit)
|
||||
.removalListener(removalListener)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches an object.
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @param value the object to cache
|
||||
*/
|
||||
public void cache(@NotNull UUID identifier, T value) {
|
||||
this.cache.put(identifier, value);
|
||||
}
|
||||
|
||||
public void invalidate(@NotNull UUID identifier) {
|
||||
this.cache.invalidate(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an object from cache.
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @return the retrieved object or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public T retrieve(@NotNull UUID identifier) {
|
||||
return cache.getIfPresent(identifier);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TemporaryPacketCache extends TemporaryCache<TimedBuffer> {
|
||||
public TemporaryPacketCache(long duration, TimeUnit timeUnit) {
|
||||
super(duration, timeUnit, (key, value, cause) -> {
|
||||
if (value == null)
|
||||
return;
|
||||
final ByteBuf buffer = value.getBuffer();
|
||||
synchronized (buffer) {
|
||||
buffer.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Object containing a {@link ByteBuf buffer} and its timestamp.
|
||||
* Used for packet-caching to use the most recent.
|
||||
*/
|
||||
public class TimedBuffer {
|
||||
|
||||
private final ByteBuf buffer;
|
||||
private final long timestamp;
|
||||
|
||||
public TimedBuffer(@NotNull ByteBuf buffer, long timestamp) {
|
||||
this.buffer = buffer;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ByteBuf getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
@ -2,25 +2,23 @@ package net.minestom.server.utils.player;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
|
||||
public final class PlayerUtils {
|
||||
|
||||
private PlayerUtils() {
|
||||
|
||||
}
|
||||
|
||||
public static boolean isNettyClient(PlayerConnection playerConnection) {
|
||||
return playerConnection instanceof NettyPlayerConnection;
|
||||
public static boolean isSocketClient(PlayerConnection playerConnection) {
|
||||
return playerConnection instanceof PlayerSocketConnection;
|
||||
}
|
||||
|
||||
public static boolean isNettyClient(Player player) {
|
||||
return isNettyClient(player.getPlayerConnection());
|
||||
public static boolean isSocketClient(Player player) {
|
||||
return isSocketClient(player.getPlayerConnection());
|
||||
}
|
||||
|
||||
public static boolean isNettyClient(Entity entity) {
|
||||
return (entity instanceof Player) && isNettyClient((Player) entity);
|
||||
public static boolean isSocketClient(Entity entity) {
|
||||
return (entity instanceof Player) && isSocketClient((Player) entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user