diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 0aec530..caa6a86 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -35,7 +35,7 @@ runs: distribution: adopt-hotspot - name: Setup maven repo - uses: s4u/maven-settings-action@v2.6.0 + uses: s4u/maven-settings-action@v3.1.0 if: ${{ inputs.perform_deploy == 'true' }} with: servers: | @@ -56,7 +56,7 @@ runs: shell: bash - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: waterdog path: target/Waterdog.jar diff --git a/Dockerfile b/Dockerfile index 19f504f..c8c8e17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-jdk-slim +FROM eclipse-temurin:17-jdk-jammy EXPOSE 19132/tcp EXPOSE 19132/udp diff --git a/README.md b/README.md index a333c35..a57e586 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,7 @@ If you haven't used WaterdogPE before, we recommend you to take a look at our [s *Please note that the config from the old Waterdog (Bungee) is not compatible with WaterdogPE* ### Supported Software -Our focus lies on PowerNukkitX 2.0 since its the only server software that's worth using. (Besides Nukkit Mot maybe?) - -You can find list of currently supported/unsupported software [here](https://docs.waterdog.dev/books/waterdogpe-setup/page/software-compatibility). +Our focus lies on PowerNukkitX since its the only server software that's worth using. ## Compiling diff --git a/pom.xml b/pom.xml index 72ddf9b..b8b983a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,14 +8,14 @@ xyz.syodo.software syodogpe - 2.0.0 + 2.0.4 jar UTF-8 17 1.0.0.CR3-SNAPSHOT - 3.0.0.Beta5-SNAPSHOT + 3.0.0.Beta11-SNAPSHOT 2.17.1 3.23.0 4.1.101.Final @@ -67,7 +67,7 @@ org.yaml snakeyaml - 1.31 + 1.32 compile @@ -85,7 +85,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.34 compile @@ -135,12 +135,6 @@ ${jline.version} compile - - com.google.guava - guava - 33.1.0-jre - compile - net.minecrell terminalconsoleappender diff --git a/src/main/java/dev/waterdog/waterdogpe/VersionInfo.java b/src/main/java/dev/waterdog/waterdogpe/VersionInfo.java index a816714..c163dd9 100644 --- a/src/main/java/dev/waterdog/waterdogpe/VersionInfo.java +++ b/src/main/java/dev/waterdog/waterdogpe/VersionInfo.java @@ -22,9 +22,9 @@ public class VersionInfo { public static final boolean DEFAULT_DEBUG = true; - private final String baseVersion = "2.0.2"; - private final String buildVersion = "syo"; - private final String author = "WaterdogTEAM, Syodo Development"; + private final String baseVersion = "2.0.4"; + private final String buildVersion = "#build"; + private final String author = "WaterdogTEAM"; private final int metricsId = 15678; private final int latestProtocolVersion; diff --git a/src/main/java/dev/waterdog/waterdogpe/event/EventPriority.java b/src/main/java/dev/waterdog/waterdogpe/event/EventPriority.java index 2790c81..41ff666 100644 --- a/src/main/java/dev/waterdog/waterdogpe/event/EventPriority.java +++ b/src/main/java/dev/waterdog/waterdogpe/event/EventPriority.java @@ -18,8 +18,8 @@ /** * Represents the Priority of an event. * Default event priority, if not specific otherwise, is NORMAL. - * HIGHEST is called first, - * LOWEST is called last. + * LOWEST is called first, + * HIGHEST is called last. */ public enum EventPriority { LOWEST, diff --git a/src/main/java/dev/waterdog/waterdogpe/event/defaults/PlayerDisconnectedEvent.java b/src/main/java/dev/waterdog/waterdogpe/event/defaults/PlayerDisconnectedEvent.java index 05bf3e4..ef04f0a 100644 --- a/src/main/java/dev/waterdog/waterdogpe/event/defaults/PlayerDisconnectedEvent.java +++ b/src/main/java/dev/waterdog/waterdogpe/event/defaults/PlayerDisconnectedEvent.java @@ -26,11 +26,20 @@ @AsyncEvent public class PlayerDisconnectedEvent extends PlayerEvent { - private final String reason; + private final CharSequence reason; - public PlayerDisconnectedEvent(ProxiedPlayer player, String reason) { + public PlayerDisconnectedEvent(ProxiedPlayer player, CharSequence reason) { super(player); - this.reason = reason; } + + + public String getReason() { + return this.getReason(String.class); + } + + public T getReason(Class type) { + return type.cast(this.reason); + } + } diff --git a/src/main/java/dev/waterdog/waterdogpe/event/defaults/PreClientDataSetEvent.java b/src/main/java/dev/waterdog/waterdogpe/event/defaults/PreClientDataSetEvent.java index f8f9486..cfcc260 100644 --- a/src/main/java/dev/waterdog/waterdogpe/event/defaults/PreClientDataSetEvent.java +++ b/src/main/java/dev/waterdog/waterdogpe/event/defaults/PreClientDataSetEvent.java @@ -22,6 +22,7 @@ import lombok.Setter; import java.security.KeyPair; +import java.util.UUID; /** * Called right when we decoded the player's LoginPacket data in the handshake(HandshakeUpstreamHandler). @@ -32,13 +33,20 @@ public class PreClientDataSetEvent extends Event { private final ProxiedConnection connection; private final JsonObject clientData; - private final JsonObject extraData; + @Getter + private final String xuid; + @Getter + private final UUID uuid; + @Getter + private final String displayName; @Setter private KeyPair keyPair; - public PreClientDataSetEvent(JsonObject clientData, JsonObject extraData, KeyPair keyPair, ProxiedConnection playerSession) { + public PreClientDataSetEvent(JsonObject clientData, String xuid, UUID uuid, String displayName, KeyPair keyPair, ProxiedConnection playerSession) { this.clientData = clientData; - this.extraData = extraData; + this.xuid = xuid; + this.uuid = uuid; + this.displayName = displayName; this.connection = playerSession; this.keyPair = keyPair; } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/BedrockServerSession.java b/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/BedrockServerSession.java index 324fba2..0c362b6 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/BedrockServerSession.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/BedrockServerSession.java @@ -77,7 +77,7 @@ public void sendPacketImmediately(BedrockPacket packet) { } @Override - public void disconnect(String reason, boolean hideReason) { + public void disconnect(CharSequence reason, boolean hideReason) { this.checkForClosed(); DisconnectPacket packet = new DisconnectPacket(); @@ -128,8 +128,10 @@ public BedrockPacketHandler getPacketHandler() { return this.packetHandler; } - public void addDisconnectListener(Consumer listener) { - this.getPeer().getChannel().closeFuture().addListener(future -> listener.accept(this.getDisconnectReason())); + public void addDisconnectListener(Consumer listener) { + this.getPeer().getChannel().closeFuture().addListener(future -> { + listener.accept(this.getDisconnectReason()); + }); } public int getSubClientId() { diff --git a/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/ProxiedBedrockPeer.java b/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/ProxiedBedrockPeer.java index 5e7e8e3..585abec 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/ProxiedBedrockPeer.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/connection/peer/ProxiedBedrockPeer.java @@ -120,8 +120,8 @@ public void sendPacket(BedrockBatchWrapper wrapper) { private void sendPacket0(BedrockBatchWrapper wrapper) { if (!(wrapper.getAlgorithm() instanceof PacketCompressionAlgorithm)) { wrapper.setCompressed(null); // Do not allow using unsupported algorithms when sending to client - } else if (this.version.isBefore(ProtocolVersion.MINECRAFT_PE_1_20_60) && - !Objects.equals(wrapper.getAlgorithm(), this.compressionStrategy.getDefaultCompression().getAlgorithm())) { + } else if (this.version.isBefore(ProtocolVersion.MINECRAFT_PE_1_20_60) && (this.compressionStrategy == null || + !Objects.equals(wrapper.getAlgorithm(), this.compressionStrategy.getDefaultCompression().getAlgorithm()))) { wrapper.setCompressed(null); // Before 1.20.60 dynamic compression is not supported } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java index b6fda9f..7036b51 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java @@ -112,12 +112,20 @@ public class ProtocolCodecs { HANDLED_PACKETS.add(LevelChunkPacket.class); HANDLED_PACKETS.add(ClientCheatAbilityPacket.class); HANDLED_PACKETS.add(ToastRequestPacket.class); + HANDLED_PACKETS.add(MovementEffectPacket.class); HANDLED_PACKETS.add(PlaySoundPacket.class); HANDLED_PACKETS.add(PlayerAuthInputPacket.class); HANDLED_PACKETS.add(ModalFormRequestPacket.class); HANDLED_PACKETS.add(ModalFormResponsePacket.class); HANDLED_PACKETS.add(BlockEntityDataPacket.class); - + HANDLED_PACKETS.add(InventoryTransactionPacket.class); + HANDLED_PACKETS.add(ClientboundCloseFormPacket.class); + HANDLED_PACKETS.add(UpdateEquipPacket.class); + HANDLED_PACKETS.add(CameraInstructionPacket.class); + HANDLED_PACKETS.add(MovementPredictionSyncPacket.class); + HANDLED_PACKETS.add(PlayerUpdateEntityOverridesPacket.class); + HANDLED_PACKETS.add(PlayerLocationPacket.class); + HANDLED_PACKETS.add(CameraPresetsPacket.class); } private static final List UPDATERS = new ObjectArrayList<>(); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java index 50e4846..42b4a00 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java @@ -64,6 +64,16 @@ import org.cloudburstmc.protocol.bedrock.codec.v686.Bedrock_v686; import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712; import org.cloudburstmc.protocol.bedrock.codec.v729.Bedrock_v729; +import org.cloudburstmc.protocol.bedrock.codec.v748.Bedrock_v748; +import org.cloudburstmc.protocol.bedrock.codec.v766.Bedrock_v766; +import org.cloudburstmc.protocol.bedrock.codec.v776.Bedrock_v776; +import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786; +import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800; +import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818; +import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819; +import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827; +import org.cloudburstmc.protocol.bedrock.codec.v844.Bedrock_v844; +import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859; @ToString(exclude = {"defaultCodec", "bedrockCodec"}) public enum ProtocolVersion { @@ -112,7 +122,20 @@ public enum ProtocolVersion { MINECRAFT_PE_1_21_0(685, Bedrock_v685.CODEC), MINECRAFT_PE_1_21_2(686, Bedrock_v686.CODEC), MINECRAFT_PE_1_21_20(712, Bedrock_v712.CODEC), - MINECRAFT_PE_1_21_30(729, Bedrock_v729.CODEC); + MINECRAFT_PE_1_21_30(729, Bedrock_v729.CODEC), + MINECRAFT_PE_1_21_40(748, Bedrock_v748.CODEC), + MINECRAFT_PE_1_21_50_29(765, 766, Bedrock_v766.CODEC), + MINECRAFT_PE_1_21_50(766, Bedrock_v766.CODEC), + MINECRAFT_PE_1_21_60(776, Bedrock_v776.CODEC), + MINECRAFT_PE_1_21_70(786, Bedrock_v786.CODEC), + MINECRAFT_PE_1_21_80(800, Bedrock_v800.CODEC), + MINECRAFT_PE_1_21_90(818, Bedrock_v818.CODEC), + MINECRAFT_PE_1_21_93(819, Bedrock_v819.CODEC), + MINECRAFT_PE_1_21_100(827, Bedrock_v827.CODEC), + MINECRAFT_PE_1_21_110(843, 844, Bedrock_v844.CODEC), + MINECRAFT_PE_1_21_111(844, Bedrock_v844.CODEC), + MINECRAFT_PE_1_21_120(859, Bedrock_v859.CODEC), + ; private static final ProtocolVersion[] VALUES = values(); private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/ProxyBatchBridge.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/ProxyBatchBridge.java index e30b210..cf48ac1 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/ProxyBatchBridge.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/ProxyBatchBridge.java @@ -28,6 +28,7 @@ import org.cloudburstmc.protocol.bedrock.netty.BedrockPacketWrapper; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketHandler; +import org.cloudburstmc.protocol.bedrock.packet.DisconnectPacket; import org.cloudburstmc.protocol.common.PacketSignal; import org.cloudburstmc.protocol.common.util.Preconditions; @@ -53,7 +54,11 @@ public void onBedrockBatch(ProxiedConnection source, BedrockBatchWrapper batch) while (iterator.hasNext()) { BedrockPacketWrapper wrapper = iterator.next(); if (wrapper.getPacket() == null) { - this.decodePacket(wrapper, source.getPacketDirection()); + try { + this.decodePacket(wrapper, source.getPacketDirection()); + } catch (Exception e) { + source.sendPacket(new DisconnectPacket()); + } } PacketSignal signal = this.handlePacket(wrapper.getPacket()); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/AbstractDownstreamHandler.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/AbstractDownstreamHandler.java index 4ab3f65..7f23255 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/AbstractDownstreamHandler.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/AbstractDownstreamHandler.java @@ -22,10 +22,21 @@ import dev.waterdog.waterdogpe.network.protocol.rewrite.RewriteMaps; import dev.waterdog.waterdogpe.player.ProxiedPlayer; import dev.waterdog.waterdogpe.network.protocol.Signals; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraPreset; +import org.cloudburstmc.protocol.bedrock.data.command.CommandData; +import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumConstraint; +import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData; import org.cloudburstmc.protocol.bedrock.data.command.*; +import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleNamedDefinition; import org.cloudburstmc.protocol.bedrock.netty.BedrockBatchWrapper; import org.cloudburstmc.protocol.bedrock.packet.*; +import org.cloudburstmc.protocol.common.NamedDefinition; import org.cloudburstmc.protocol.common.PacketSignal; +import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry; import java.util.*; import java.util.function.Consumer; @@ -42,6 +53,18 @@ public AbstractDownstreamHandler(ProxiedPlayer player, ClientConnection connecti this.connection = connection; } + @Override + public PacketSignal handle(ItemComponentPacket packet) { + if (!this.player.acceptItemComponentPacket()) { + return Signals.CANCEL; + } + player.setAcceptItemComponentPacket(false); + if (this.player.getProtocol().isAfterOrEqual(ProtocolVersion.MINECRAFT_PE_1_21_60)) { + setItemDefinitions(packet.getItems()); + } + return PacketSignal.UNHANDLED; + } + @Override public void sendProxiedBatch(BedrockBatchWrapper batch) { if (this.player.getConnection().isConnected()) { @@ -68,6 +91,7 @@ public PacketSignal handle(AvailableCommandsPacket packet) { for (Command command : this.player.getProxy().getCommandMap().getCommands().values()) { if (command.getPermission() == null || this.player.hasPermission(command.getPermission())) { + packet.getCommands().stream().filter(commandData -> commandData.getName().equalsIgnoreCase(command.getName())).findFirst().ifPresent(commandData -> packet.getCommands().remove(commandData)); packet.getCommands().add(command.getCommandData()); } } @@ -80,7 +104,6 @@ public PacketSignal handle(AvailableCommandsPacket packet) { ListIterator iterator = packet.getCommands().listIterator(); while (iterator.hasNext()) { CommandData command = iterator.next(); - if (command.getAliases() != null) { continue; } @@ -96,6 +119,16 @@ public PacketSignal handle(AvailableCommandsPacket packet) { Collections.emptyList(), command.getOverloads())); } + + for(CommandData command : packet.getCommands()) { + for(CommandOverloadData overload : command.getOverloads()) { + for(CommandParamData param : overload.getOverloads()) { + if(param.getType() == null) { + param.setType(CommandParam.UNKNOWN); + } + } + } + } return PacketSignal.HANDLED; } @@ -119,6 +152,12 @@ public PacketSignal handle(ClientCacheMissResponsePacket packet) { return PacketSignal.UNHANDLED; } + @Override + public PacketSignal handle(CameraPresetsPacket packet) { + setCameraPresetDefinitions(packet.getPresets()); + return PacketSignal.UNHANDLED; + } + protected PacketSignal onPlayStatus(PlayStatusPacket packet, Consumer failedTask, ClientConnection connection) { String message; switch (packet.getStatus()) { @@ -148,4 +187,32 @@ public RewriteMaps getRewriteMaps() { public ClientConnection getConnection() { return connection; } + + protected void setItemDefinitions(Collection definitions) { + BedrockCodecHelper codecHelper = this.player.getConnection() + .getPeer() + .getCodecHelper(); + SimpleDefinitionRegistry.Builder itemRegistry = SimpleDefinitionRegistry.builder(); + IntSet runtimeIds = new IntOpenHashSet(); + for (ItemDefinition definition : definitions) { + if (runtimeIds.add(definition.getRuntimeId())) { + itemRegistry.add(definition); + } else { + player.getLogger().warning("[{}|{}] has duplicate item definition: {}", this.player.getName(), this.connection.getServerInfo().getServerName(), definition); + } + } + codecHelper.setItemDefinitions(itemRegistry.build()); + } + + protected void setCameraPresetDefinitions(Collection presets) { + BedrockCodecHelper codecHelper = this.player.getConnection() + .getPeer() + .getCodecHelper(); + SimpleDefinitionRegistry.Builder registry = SimpleDefinitionRegistry.builder(); + int id = 0; + for (CameraPreset preset : presets) { + registry.add(new SimpleNamedDefinition(preset.getIdentifier(), id++)); + } + codecHelper.setCameraPresetDefinitions(registry.build()); + } } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/InitialHandler.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/InitialHandler.java index 3796c6a..7524f53 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/InitialHandler.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/InitialHandler.java @@ -19,11 +19,7 @@ import dev.waterdog.waterdogpe.network.connection.client.ClientConnection; import dev.waterdog.waterdogpe.network.connection.handler.ReconnectReason; import dev.waterdog.waterdogpe.network.protocol.registry.FakeDefinitionRegistry; -import dev.waterdog.waterdogpe.network.protocol.user.HandshakeUtils; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; -import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.packet.*; import dev.waterdog.waterdogpe.event.defaults.InitialServerConnectedEvent; import dev.waterdog.waterdogpe.network.serverinfo.ServerInfo; @@ -37,7 +33,6 @@ import dev.waterdog.waterdogpe.utils.types.TranslationContainer; import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils; import org.cloudburstmc.protocol.common.PacketSignal; -import org.cloudburstmc.protocol.common.SimpleDefinitionRegistry; import javax.crypto.SecretKey; import java.net.URI; @@ -66,7 +61,7 @@ public final PacketSignal handle(ServerToClientHandshakePacket packet) { try { SignedJWT saltJwt = SignedJWT.parse(packet.getJwt()); URI x5u = saltJwt.getHeader().getX509CertURL(); - ECPublicKey serverKey = HandshakeUtils.generateKey(x5u.toASCIIString()); + ECPublicKey serverKey = EncryptionUtils.parseKey(x5u.toASCIIString()); SecretKey key = EncryptionUtils.getSecretKey( this.player.getLoginData().getKeyPair().getPrivate(), serverKey, @@ -130,17 +125,10 @@ public final PacketSignal handle(StartGamePacket packet) { BedrockCodecHelper codecHelper = this.player.getConnection() .getPeer() .getCodecHelper(); - // Setup item registry - SimpleDefinitionRegistry.Builder itemRegistry = SimpleDefinitionRegistry.builder(); - IntSet runtimeIds = new IntOpenHashSet(); - for (ItemDefinition definition : packet.getItemDefinitions()) { - if (runtimeIds.add(definition.getRuntimeId())) { - itemRegistry.add(definition); - } else { - this.player.getLogger().warning("[{}|{}] has duplicate item definition: {}", this.player.getName(), this.connection.getServerInfo().getServerName(), definition); - } + // Setup item registry. After 1.21.60 these are sent with ItemComponentPacket instead. + if (this.player.getProtocol().isBeforeOrEqual(ProtocolVersion.MINECRAFT_PE_1_21_50)) { + setItemDefinitions(packet.getItemDefinitions()); } - codecHelper.setItemDefinitions(itemRegistry.build()); // Setup block registry codecHelper.setBlockDefinitions(FakeDefinitionRegistry.createBlockRegistry()); // Enable runtimeId rewrite diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/SwitchDownstreamHandler.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/SwitchDownstreamHandler.java index aff0e4c..1bd3729 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/SwitchDownstreamHandler.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/downstream/SwitchDownstreamHandler.java @@ -18,7 +18,6 @@ import com.nimbusds.jwt.SignedJWT; import dev.waterdog.waterdogpe.network.connection.client.ClientConnection; import dev.waterdog.waterdogpe.network.protocol.handler.TransferCallback; -import dev.waterdog.waterdogpe.network.protocol.user.HandshakeUtils; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import lombok.extern.log4j.Log4j2; import org.cloudburstmc.math.vector.Vector3f; @@ -58,7 +57,7 @@ public final PacketSignal handle(ServerToClientHandshakePacket packet) { try { SignedJWT saltJwt = SignedJWT.parse(packet.getJwt()); URI x5u = saltJwt.getHeader().getX509CertURL(); - ECPublicKey serverKey = HandshakeUtils.generateKey(x5u.toASCIIString()); + ECPublicKey serverKey = EncryptionUtils.parseKey(x5u.toASCIIString()); SecretKey key = EncryptionUtils.getSecretKey( this.player.getLoginData().getKeyPair().getPrivate(), serverKey, diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/AbstractUpstreamHandler.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/AbstractUpstreamHandler.java index 67df032..2996963 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/AbstractUpstreamHandler.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/AbstractUpstreamHandler.java @@ -58,7 +58,7 @@ public PacketSignal handle(ClientCacheStatusPacket packet) { @Override public final PacketSignal handle(PacketViolationWarningPacket packet) { this.player.getLogger().warning("Received violation from " + this.player.getName() + ": " + packet.toString()); - return this.cancel(); + return PacketSignal.HANDLED; } /** diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/LoginUpstreamHandler.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/LoginUpstreamHandler.java index e2aa4dd..6bad9ab 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/LoginUpstreamHandler.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/handler/upstream/LoginUpstreamHandler.java @@ -146,6 +146,9 @@ public PacketSignal handle(LoginPacket packet) { this.session.setLogging(WaterdogPE.version().debug()); try { + this.proxy.getLogger().debug("[{}] <-> Received login with authType: {} and payloadType: {}.", this.session.getSocketAddress(), + packet.getAuthPayload().getClass().getSimpleName(), packet.getAuthPayload().getAuthType()); + handshakeEntry = HandshakeUtils.processHandshake(this.session, packet, protocol, strictAuth); if (!handshakeEntry.isXboxAuthed() && strictAuth) { this.onLoginFailed(handshakeEntry, null, "disconnectionScreen.notAuthenticated"); @@ -155,7 +158,7 @@ public PacketSignal handle(LoginPacket packet) { // Thank you Mojang: this version includes protocol changes, but protocol version was not increased. if (protocol.equals(ProtocolVersion.MINECRAFT_PE_1_19_60) && handshakeEntry.getClientData().has("GameVersion") && - ProtocolVersion.MINECRAFT_PE_1_19_62.getMinecraftVersion().equals(handshakeEntry.getClientData().get("GameVersion").getAsString())) {; + ProtocolVersion.MINECRAFT_PE_1_19_62.getMinecraftVersion().equals(handshakeEntry.getClientData().get("GameVersion").getAsString())) { handshakeEntry.setProtocol(protocol = ProtocolVersion.MINECRAFT_PE_1_19_62); this.session.getPeer().setProtocol(protocol); } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java index 15a1df4..65a262b 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java @@ -16,6 +16,7 @@ package dev.waterdog.waterdogpe.network.protocol.rewrite; import it.unimi.dsi.fastutil.longs.LongListIterator; +import org.cloudburstmc.protocol.bedrock.data.camera.CameraAttachToEntityInstruction; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; @@ -220,6 +221,11 @@ public PacketSignal handle(InteractPacket packet) { return rewriteId(packet.getRuntimeEntityId(), packet::setRuntimeEntityId); } + @Override + public PacketSignal handle(PlayerLocationPacket packet) { + return rewriteId(packet.getTargetEntityId(), packet::setTargetEntityId); + } + @Override public PacketSignal handle(SetEntityLinkPacket packet) { EntityLinkData entityLink = packet.getEntityLink(); @@ -321,6 +327,16 @@ public PacketSignal handle(ClientCheatAbilityPacket packet) { return rewriteId(packet.getUniqueEntityId(), packet::setUniqueEntityId); } + @Override + public PacketSignal handle(PlayerUpdateEntityOverridesPacket packet) { + return rewriteId(packet.getEntityUniqueId(), packet::setEntityUniqueId); + } + + @Override + public PacketSignal handle(LevelSoundEventPacket packet) { + return rewriteId(packet.getEntityUniqueId(), packet::setEntityUniqueId); + } + @Override public PacketSignal handle(AnimateEntityPacket packet) { PacketSignal signal = PacketSignal.UNHANDLED; @@ -332,11 +348,37 @@ public PacketSignal handle(AnimateEntityPacket packet) { return signal; } + @Override + public PacketSignal handle(MovementEffectPacket packet) { + return rewriteId(packet.getEntityRuntimeId(), packet::setEntityRuntimeId); + } + + @Override + public PacketSignal handle(MovementPredictionSyncPacket packet) { + return rewriteId(packet.getRuntimeEntityId(), packet::setRuntimeEntityId); + } + + @Override + public PacketSignal handle(UpdateEquipPacket packet) { + return rewriteId(packet.getUniqueEntityId(), packet::setUniqueEntityId); + } + + @Override + public PacketSignal handle(CameraInstructionPacket packet) { + PacketSignal signal = PacketSignal.UNHANDLED; + CameraAttachToEntityInstruction attachInstruction = packet.getAttachInstruction(); + if (attachInstruction != null) { + PacketSignal returnedSignal = rewriteId(attachInstruction.getUniqueEntityId(), attachInstruction::setUniqueEntityId); + signal = mergeSignals(signal, returnedSignal); + } + return signal; + } + private PacketSignal rewriteMetadata(EntityDataMap metadata) { PacketSignal signal = PacketSignal.UNHANDLED; for (EntityDataType data : ENTITY_DATA_FIELDS) { Long id = metadata.get(data); - if (id != null && id > 0L) { // IDs start at 1, so this is safe + if (id != null) { long rewriteId = PlayerRewriteUtils.rewriteId(id, this.rewrite.getEntityId(), this.rewrite.getOriginalEntityId()); if (rewriteId != id) { metadata.put(data, rewriteId); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java index da4d297..650b47b 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java @@ -20,7 +20,6 @@ import dev.waterdog.waterdogpe.event.defaults.PreClientDataSetEvent; import dev.waterdog.waterdogpe.network.connection.peer.BedrockServerSession; import dev.waterdog.waterdogpe.network.protocol.ProtocolVersion; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils; @@ -29,33 +28,46 @@ import java.util.UUID; @Getter -@AllArgsConstructor public class HandshakeEntry { private final ECPublicKey identityPublicKey; private final JsonObject clientData; - private final JsonObject extraData; + private final String xuid; + private final UUID uuid; + private final String displayName; private final boolean xboxAuthed; @Setter private ProtocolVersion protocol; + private final boolean isChainPayload; - public LoginData buildData(BedrockServerSession session, ProxyServer proxy) { + public HandshakeEntry(ECPublicKey identityPublicKey, JsonObject clientData, String xuid, UUID uuid, String displayName, boolean xboxAuthed, ProtocolVersion protocol, boolean isChainPayload) { + this.identityPublicKey = identityPublicKey; + this.clientData = clientData; + this.xuid = xuid; + this.uuid = uuid; + this.displayName = displayName; + this.xboxAuthed = xboxAuthed; + this.protocol = protocol; + this.isChainPayload = isChainPayload; + } + + public LoginData buildData(BedrockServerSession session, ProxyServer proxy) throws Exception { // This is first event which exposes new player connecting to proxy. // The purpose is to change player's client data or set encryption keypair before joining first downstream. - PreClientDataSetEvent event = new PreClientDataSetEvent(this.clientData, this.extraData, EncryptionUtils.createKeyPair(), session); + PreClientDataSetEvent event = new PreClientDataSetEvent(this.clientData, this.xuid, this.uuid, this.displayName, EncryptionUtils.createKeyPair(), session); proxy.getEventManager().callEvent(event); LoginData.LoginDataBuilder builder = LoginData.builder(); - builder.displayName(this.extraData.get("displayName").getAsString()); - builder.uuid(UUID.fromString(this.extraData.get("identity").getAsString())); - builder.xuid(this.extraData.get("XUID").getAsString()); + builder.displayName(this.displayName); + builder.uuid(this.uuid); + builder.xuid(this.xuid); builder.xboxAuthed(this.xboxAuthed); builder.protocol(this.protocol); builder.joinHostname(this.clientData.get("ServerAddress").getAsString().split(":")[0]); builder.address(session.getSocketAddress()); builder.keyPair(event.getKeyPair()); builder.clientData(this.clientData); - builder.extraData(this.extraData); + builder.isChainPayload(this.isChainPayload); if (this.clientData.has("DeviceModel")) { builder.deviceModel(this.clientData.get("DeviceModel").getAsString()); } @@ -68,7 +80,4 @@ public LoginData buildData(BedrockServerSession session, ProxyServer proxy) { return builder.build(); } - public String getDisplayName() { - return this.extraData.get("displayName").getAsString(); - } } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java index e4eb9d5..9826b44 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java @@ -15,9 +15,7 @@ package dev.waterdog.waterdogpe.network.protocol.user; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.*; import com.nimbusds.jose.*; import com.nimbusds.jose.crypto.ECDSASigner; import com.nimbusds.jose.crypto.ECDSAVerifier; @@ -28,11 +26,13 @@ import dev.waterdog.waterdogpe.network.protocol.ProtocolVersion; import dev.waterdog.waterdogpe.utils.config.proxy.ProxyConfig; import lombok.Getter; +import lombok.extern.log4j.Log4j2; import org.cloudburstmc.protocol.bedrock.BedrockSession; +import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket; +import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult; import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils; -import org.cloudburstmc.protocol.common.util.Preconditions; import javax.crypto.SecretKey; import java.net.InetSocketAddress; @@ -40,22 +40,16 @@ import java.security.*; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.Base64; -import java.util.Iterator; -import java.util.List; import java.util.UUID; /** * Various utilities for parsing Handshake data */ +@Log4j2 public class HandshakeUtils { - private static final ECPublicKey MOJANG_PUBLIC_KEY_OLD; - private static final ECPublicKey MOJANG_PUBLIC_KEY; - @Getter private static final KeyPair privateKeyPair; @@ -64,62 +58,12 @@ public class HandshakeUtils { KeyPairGenerator generator = KeyPairGenerator.getInstance("EC"); generator.initialize(Curve.P_384.toECParameterSpec()); privateKeyPair = generator.generateKeyPair(); - - MOJANG_PUBLIC_KEY_OLD = generateKey("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V"); - MOJANG_PUBLIC_KEY = generateKey("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp"); } catch (Exception e) { throw new RuntimeException("Unable to generate private keyPair!", e); } } - public static boolean validateChain(List chainArray, boolean strict) throws Exception { - if (strict && chainArray.size() > 3) { - // We dont expect larger chain - return false; - } - - ECPublicKey lastKey = null; - boolean authed = false; - Iterator iterator = chainArray.iterator(); - while(iterator.hasNext()){ - SignedJWT jwt = SignedJWT.parse(iterator.next()); - - URI x5u = jwt.getHeader().getX509CertURL(); - if (x5u == null) { - throw new JOSEException("Key not found"); - } - - ECPublicKey expectedKey = generateKey(jwt.getHeader().getX509CertURL().toString()); - if (lastKey == null) { - // First key is self signed - lastKey = expectedKey; - } else if (strict && !lastKey.equals(expectedKey)) { - // Make sure the previous key matches the header of the current - throw new IllegalArgumentException("Key does not match"); - } - - if (!verifyJwt(jwt, lastKey)) { - if (strict) { - throw new JOSEException("Login JWT was not valid"); - } - return false; - } - - if (MOJANG_PUBLIC_KEY.equals(lastKey) || MOJANG_PUBLIC_KEY_OLD.equals(lastKey)) { - authed = true; - } else if (authed) { - return !iterator.hasNext(); - } - - JsonObject payload = (JsonObject) JsonParser.parseString(jwt.getPayload().toString()); - Preconditions.checkArgument(payload.has("identityPublicKey"), "IdentityPublicKey node is missing in chain!"); - JsonElement ipkNode = payload.get("identityPublicKey"); - lastKey = generateKey(ipkNode.getAsString()); - } - return authed; - } - - public static SignedJWT createExtraData(KeyPair pair, JsonObject extraData) { + public static SignedJWT createClientDataChain(KeyPair pair, JsonObject extraData) { String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); long timestamp = System.currentTimeMillis() / 1000; @@ -135,6 +79,20 @@ public static SignedJWT createExtraData(KeyPair pair, JsonObject extraData) { return encodeJWT(pair, dataChain); } + public static SignedJWT createClientDataToken(KeyPair pair, String displayName, String xuid) { + String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); + long timestamp = System.currentTimeMillis() / 1000; + + JsonObject dataChain = new JsonObject(); + dataChain.addProperty("iat", timestamp); + dataChain.addProperty("exp", timestamp + 24 * 3600); + dataChain.addProperty("iss", "self"); + dataChain.addProperty("cpk", publicKeyBase64); + dataChain.addProperty("xid", xuid); + dataChain.addProperty("xname", displayName); + return encodeJWT(pair, dataChain); + } + public static SignedJWT encodeJWT(KeyPair pair, JsonObject payload) { String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); URI x5u = URI.create(publicKeyBase64); @@ -148,10 +106,6 @@ public static SignedJWT encodeJWT(KeyPair pair, JsonObject payload) { } } - public static ECPublicKey generateKey(String b64) throws NoSuchAlgorithmException, InvalidKeySpecException { - return (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(b64))); - } - public static void signJwt(JWSObject jws, ECPrivateKey key) throws JOSEException { jws.sign(new ECDSASigner(key, Curve.P_384)); } @@ -161,54 +115,49 @@ public static boolean verifyJwt(JWSObject jws, ECPublicKey key) throws JOSEExcep } public static HandshakeEntry processHandshake(BedrockSession session, LoginPacket packet, ProtocolVersion protocol, boolean strict) throws Exception { - List chain = packet.getChain(); - if (chain.isEmpty()) { - throw new IllegalArgumentException("Invalid chain data"); + ChainValidationResult result = EncryptionUtils.validatePayload(packet.getAuthPayload()); + boolean xboxAuth = result.signed(); + ChainValidationResult.IdentityClaims identityClaims = result.identityClaims(); + ChainValidationResult.IdentityData identityData = identityClaims.extraData; + ECPublicKey identityPublicKey = (ECPublicKey) identityClaims.parsedIdentityPublicKey(); + String xuid = identityData.xuid; + //UUID uuid = UUID.nameUUIDFromBytes(("pocket-auth-1-xuid:" + xuid).getBytes(StandardCharsets.UTF_8)); + UUID uuid = identityData.identity; + + SignedJWT clientDataJwt = SignedJWT.parse(packet.getClientJwt()); + JsonObject clientData = HandshakeUtils.parseClientData(clientDataJwt, xuid, session); + if (!verifyJwt(clientDataJwt, identityPublicKey) && strict) { + xboxAuth = false; } - - boolean xboxAuth = HandshakeUtils.validateChain(chain, strict); - JsonObject payload = (JsonObject) JsonParser.parseString(SignedJWT.parse(chain.get(chain.size() - 1)).getPayload().toString()); - JsonObject extraData = HandshakeUtils.parseExtraData(packet, payload); - - if (!payload.has("identityPublicKey")) { - throw new RuntimeException("Identity Public Key was not found!"); + String displayName; + if (ProxyServer.getInstance().getConfiguration().isReplaceUsernameSpaces()) { + displayName = identityData.displayName + .replaceAll(" ", "_"); + } else { + displayName = identityData.displayName; } - String identityPublicKeyString = payload.get("identityPublicKey").getAsString(); - ECPublicKey identityPublicKey = generateKey(identityPublicKeyString); - SignedJWT extraDataJwt = SignedJWT.parse(packet.getExtra()); - if (!verifyJwt(extraDataJwt, identityPublicKey) && strict) { - xboxAuth = false; + if (xboxAuth) { + ProxyConfig config = ProxyServer.getInstance().getConfiguration(); + if (config.useLoginExtras()) { + clientData.addProperty("Waterdog_Auth", true); + } } - JsonObject clientData = HandshakeUtils.parseClientData(extraDataJwt, extraData, session); - return new HandshakeEntry(identityPublicKey, clientData, extraData, xboxAuth, protocol); + return new HandshakeEntry(identityPublicKey, clientData, xuid, uuid, displayName, xboxAuth, protocol, + packet.getAuthPayload() instanceof CertificateChainPayload); } - public static JsonObject parseClientData(JWSObject clientJwt, JsonObject extraData, BedrockSession session) { + public static JsonObject parseClientData(JWSObject clientJwt, String xuid, BedrockSession session) throws Exception { JsonObject clientData = (JsonObject) JsonParser.parseString(clientJwt.getPayload().toString()); ProxyConfig config = ProxyServer.getInstance().getConfiguration(); if (config.useLoginExtras()) { // Add waterdog attributes - clientData.addProperty("Waterdog_XUID", extraData.get("XUID").getAsString()); + clientData.addProperty("Waterdog_XUID", xuid); clientData.addProperty("Waterdog_IP", ((InetSocketAddress) session.getSocketAddress()).getAddress().getHostAddress()); } return clientData; } - public static JsonObject parseExtraData(LoginPacket packet, JsonObject payload) { - JsonElement extraDataElement = payload.get("extraData"); - if (!extraDataElement.isJsonObject()) { - throw new IllegalStateException("Invalid 'extraData'"); - } - - JsonObject extraData = extraDataElement.getAsJsonObject(); - if (ProxyServer.getInstance().getConfiguration().isReplaceUsernameSpaces()) { - String playerName = extraData.get("displayName").getAsString(); - extraData.addProperty("displayName", playerName.replaceAll(" ", "_")); - } - return extraData; - } - public static void processEncryption(BedrockSession session, PublicKey key) throws Exception { byte[] token = EncryptionUtils.generateRandomToken(); SecretKey encryptionKey = EncryptionUtils.getSecretKey(privateKeyPair.getPrivate(), key, token); @@ -221,4 +170,12 @@ public static void processEncryption(BedrockSession session, PublicKey key) thro session.enableEncryption(encryptionKey); }); } + + public static JsonObject createChainExtraData(String displayName, String xuid, UUID uuid) { + JsonObject extraData = new JsonObject(); + extraData.addProperty("displayName", displayName); + extraData.addProperty("XUID", xuid); + extraData.addProperty("identity", uuid.toString()); + return extraData; + } } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java index a858431..275b9f5 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java @@ -17,24 +17,32 @@ import com.google.gson.JsonObject; import com.nimbusds.jwt.SignedJWT; +import dev.waterdog.waterdogpe.ProxyServer; import dev.waterdog.waterdogpe.network.protocol.ProtocolVersion; import lombok.Builder; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.cloudburstmc.protocol.bedrock.data.auth.AuthType; +import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload; +import org.cloudburstmc.protocol.bedrock.data.auth.TokenPayload; import org.cloudburstmc.protocol.bedrock.packet.ClientCacheStatusPacket; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.RequestChunkRadiusPacket; import java.net.SocketAddress; import java.security.KeyPair; +import java.util.Collections; import java.util.UUID; /** * Holds relevant information passed to the proxy on the first connection (initial) in the LoginPacket. */ -@Getter +@Slf4j @Builder +@Getter public class LoginData { + private final String displayName; private final UUID uuid; private final String xuid; @@ -52,17 +60,17 @@ public class LoginData { private final KeyPair keyPair; private final JsonObject clientData; - private final JsonObject extraData; - private LoginPacket loginPacket; - @Builder.Default @Setter - private RequestChunkRadiusPacket chunkRadius = PlayerRewriteUtils.defaultChunkRadius; @Builder.Default + private RequestChunkRadiusPacket chunkRadius = PlayerRewriteUtils.defaultChunkRadius; @Setter + @Builder.Default private ClientCacheStatusPacket cachePacket = PlayerRewriteUtils.defaultCachePacket; + private final boolean isChainPayload; + /** * Used to construct new login packet using this.clientData and this.extraData signed by this.keyPair. * This method should be called everytime client data is changed. Otherwise player will join to downstream using old data. @@ -70,14 +78,20 @@ public class LoginData { * @return new LoginPacket. */ public LoginPacket rebuildLoginPacket() { - SignedJWT signedClientData = HandshakeUtils.createExtraData(this.keyPair, this.extraData); - SignedJWT signedExtraData = HandshakeUtils.encodeJWT(this.keyPair, this.clientData); - LoginPacket loginPacket = new LoginPacket(); - loginPacket.getChain().add(signedClientData.serialize()); - loginPacket.setExtra(signedExtraData.serialize()); + SignedJWT signedClientData = HandshakeUtils.encodeJWT(this.keyPair, this.clientData); + loginPacket.setClientJwt(signedClientData.serialize()); loginPacket.setProtocolVersion(this.protocol.getProtocol()); - return this.loginPacket = loginPacket; + if (isChainPayload || ProxyServer.getInstance().getConfiguration().useCertificatePayload()) { + JsonObject extraData = HandshakeUtils.createChainExtraData(displayName, xuid, uuid); + SignedJWT signedPayload = HandshakeUtils.createClientDataChain(this.keyPair, extraData); + loginPacket.setAuthPayload(new CertificateChainPayload(Collections.singletonList(signedPayload.serialize()), AuthType.SELF_SIGNED)); + } else { + SignedJWT signedPayload = HandshakeUtils.createClientDataToken(this.keyPair, displayName, xuid); + loginPacket.setAuthPayload(new TokenPayload(signedPayload.serialize(), AuthType.SELF_SIGNED)); + } + this.loginPacket = loginPacket; + return loginPacket; } public LoginPacket getLoginPacket() { @@ -86,4 +100,5 @@ public LoginPacket getLoginPacket() { } return this.loginPacket; } + } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/PlayerRewriteUtils.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/PlayerRewriteUtils.java index b246b6e..b38879a 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/PlayerRewriteUtils.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/PlayerRewriteUtils.java @@ -362,6 +362,6 @@ public static void injectEntityImmobile(ProxiedConnection session, long runtimeI } public static boolean checkForImmobileFlag(EntityDataMap dataMap) { - return dataMap != null && dataMap.getFlags() != null && dataMap.getFlags().contains(EntityFlag.NO_AI); + return dataMap != null && dataMap.getFlags() != null && dataMap.getFlags().containsKey(EntityFlag.NO_AI); } } diff --git a/src/main/java/dev/waterdog/waterdogpe/packs/PackManager.java b/src/main/java/dev/waterdog/waterdogpe/packs/PackManager.java index 6e2ab02..53a9881 100644 --- a/src/main/java/dev/waterdog/waterdogpe/packs/PackManager.java +++ b/src/main/java/dev/waterdog/waterdogpe/packs/PackManager.java @@ -149,6 +149,8 @@ public boolean unregisterPack(UUID packId) { public void rebuildPackets() { this.packsInfoPacket.setForcedToAccept(this.proxy.getConfiguration().isForceServerPacks()); + this.packsInfoPacket.setWorldTemplateId(UUID.randomUUID()); + this.packsInfoPacket.setWorldTemplateVersion(""); this.stackPacket.setForcedToAccept(this.proxy.getConfiguration().isOverwriteClientPacks()); this.packsInfoPacket.getBehaviorPackInfos().clear(); @@ -160,8 +162,8 @@ public void rebuildPackets() { this.stackPacket.setGameVersion(""); for (ResourcePack pack : this.packs.values()) { - ResourcePacksInfoPacket.Entry infoEntry = new ResourcePacksInfoPacket.Entry(pack.getPackId().toString(), pack.getVersion().toString(), - pack.getPackSize(), pack.getContentKey(), "", pack.getContentKey().equals("") ? "" : pack.getPackId().toString(), false, false, false); + ResourcePacksInfoPacket.Entry infoEntry = new ResourcePacksInfoPacket.Entry(pack.getPackId(), pack.getVersion().toString(), + pack.getPackSize(), pack.getContentKey(), "", pack.getContentKey().equals("") ? "" : pack.getPackId().toString(), false, false, false, null); ResourcePackStackPacket.Entry stackEntry = new ResourcePackStackPacket.Entry(pack.getPackId().toString(), pack.getVersion().toString(), ""); if (pack.getType().equals(ResourcePack.TYPE_RESOURCES)) { this.packsInfoPacket.getResourcePackInfos().add(infoEntry); diff --git a/src/main/java/dev/waterdog/waterdogpe/player/ProxiedPlayer.java b/src/main/java/dev/waterdog/waterdogpe/player/ProxiedPlayer.java index 5cc71f5..a5d49b4 100644 --- a/src/main/java/dev/waterdog/waterdogpe/player/ProxiedPlayer.java +++ b/src/main/java/dev/waterdog/waterdogpe/player/ProxiedPlayer.java @@ -69,7 +69,8 @@ public class ProxiedPlayer implements CommandSender { private final AtomicBoolean disconnected = new AtomicBoolean(false); private final AtomicBoolean loginCalled = new AtomicBoolean(false); private final AtomicBoolean loginCompleted = new AtomicBoolean(false); - private volatile String disconnectReason; + private volatile CharSequence disconnectReason; + private final RewriteData rewriteData = new RewriteData(); private final LoginData loginData; private final RewriteMaps rewriteMaps; @@ -117,6 +118,11 @@ public class ProxiedPlayer implements CommandSender { */ @Getter(AccessLevel.NONE) private volatile boolean acceptResourcePacks = true; + /** + * Used to determine if proxy can send ItemComponentPacket to player. + * Client will crash if ItemComponentPacket is sent twice. + */ + private volatile boolean acceptItemComponentPacket = true; /** * Additional downstream and upstream handlers can be set by plugin. * Do not set directly BedrockPacketHandler to sessions! @@ -244,6 +250,7 @@ public void connect(ServerInfo serverInfo) { ClientConnection connectingServer = this.getPendingConnection(); if (connectingServer != null) { if (connectingServer.getServerInfo() == targetServer) { + this.pendingServers.remove(targetServer); this.sendMessage(new TranslationContainer("waterdog.downstream.connecting", targetServer.getServerName())); return; } else { @@ -348,7 +355,7 @@ public void disconnect(TextContainer message) { * * @param reason The disconnect reason the player will see on his disconnect screen (Supports Color Codes) */ - public void disconnect(String reason) { + public void disconnect(CharSequence reason) { if (this.loginCalled.get() && !this.loginCompleted.get()) { // Wait until PlayerLoginEvent completes this.disconnectReason = reason; @@ -738,6 +745,24 @@ public Collection getPermissions() { return Collections.unmodifiableCollection(this.permissions.values()); } + /** + * @return true if the player has administrator status, false if not + */ + public boolean isAdmin() { + return this.admin; + } + + /** + * Sets whether this player should have Administrator Status. + * Players with administrator status are granted every permissions, even if not specificly applied + * + * @param admin Whether the player is admin or not + */ + public void setAdmin(boolean admin) { + this.admin = admin; + } + + @Override public boolean isPlayer() { return true; @@ -760,7 +785,7 @@ public ServerInfo getServerInfo() { public InetSocketAddress getAddress() { return this.connection == null ? null : (InetSocketAddress) this.connection.getSocketAddress(); } - + public String getHostAddress() { InetSocketAddress address = this.getAddress(); return address == null ? null : address.getAddress().getHostAddress(); @@ -802,10 +827,22 @@ public ServerInfo getConnectingServer() { return this.pendingConnection == null ? null : this.pendingConnection.getServerInfo(); } + public BedrockServerSession getConnection() { + return this.connection; + } + public boolean isConnected() { return !this.disconnected.get() && this.connection != null && this.connection.isConnected(); } + public RewriteMaps getRewriteMaps() { + return this.rewriteMaps; + } + + public LoginData getLoginData() { + return this.loginData; + } + @Override public String getName() { return this.loginData.getDisplayName(); @@ -835,6 +872,10 @@ public ProtocolVersion getProtocol() { return this.loginData.getProtocol(); } + public RewriteData getRewriteData() { + return this.rewriteData; + } + public void setCanRewrite(boolean canRewrite) { this.canRewrite = canRewrite; } @@ -847,10 +888,34 @@ public boolean hasUpstreamBridge() { return this.hasUpstreamBridge; } + public LongSet getEntities() { + return this.entities; + } + + public LongSet getBossbars() { + return this.bossbars; + } + public Collection getPlayers() { return this.players; } + public ObjectSet getScoreboards() { + return this.scoreboards; + } + + public Long2ObjectMap getScoreInfos() { + return this.scoreInfos; + } + + public Long2LongMap getEntityLinks() { + return this.entityLinks; + } + + public LongSet getChunkBlobs() { + return this.chunkBlobs; + } + public void setAcceptPlayStatus(boolean acceptPlayStatus) { this.acceptPlayStatus = acceptPlayStatus; } @@ -866,6 +931,7 @@ public boolean acceptResourcePacks() { public Object getData(String key) { return this.data.get(key); } + public Object getData(String key, Object fallback) { if(this.hasData(key)) { return this.data.get(key); @@ -881,12 +947,36 @@ public void setData(String key, Object value) { this.data.put(key, value); } + public boolean acceptItemComponentPacket() { + return acceptItemComponentPacket; + } + + public void setAcceptItemComponentPacket(boolean acceptItemComponentPacket) { + this.acceptItemComponentPacket = acceptItemComponentPacket; + } + + public CompressionType getCompression() { + return this.compression; + } + + public Collection getPluginPacketHandlers() { + return this.pluginPacketHandlers; + } + + public String getDisconnectReason() { + return this.getDisconnectReason(String.class); + } + + public T getDisconnectReason(Class type) { + return type.cast(this.disconnectReason); + } + @Override public String toString() { return "ProxiedPlayer(displayName=" + this.getName() + ", protocol=" + this.getProtocol() + ", connected=" + this.isConnected() + - ", address=" + this.getHostAddress() + + ", address=" + this.getAddress() + ", serverInfo=" + this.getServerInfo() + ")"; } diff --git a/src/main/java/dev/waterdog/waterdogpe/plugin/Plugin.java b/src/main/java/dev/waterdog/waterdogpe/plugin/Plugin.java index ca5a428..ac74125 100644 --- a/src/main/java/dev/waterdog/waterdogpe/plugin/Plugin.java +++ b/src/main/java/dev/waterdog/waterdogpe/plugin/Plugin.java @@ -48,7 +48,6 @@ public abstract class Plugin { protected boolean enabled = false; private PluginYAML description; private ProxyServer proxy; - @Getter(AccessLevel.NONE) private Logger logger; private File pluginFile; private File dataFolder; diff --git a/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java b/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java index 8770f49..1920db6 100644 --- a/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java +++ b/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java @@ -117,6 +117,11 @@ public class ProxyConfig extends YamlConfig { @Comment("If enabled, the proxy will pass information like XUID or IP to the downstream server using custom fields in the LoginPacket") private boolean useLoginExtras = false; + @Path("use_certificate_payload") + @Accessors(fluent = true) + @Comment("If enabled, the proxy will always send Certificate payload in the LoginPacket") + private boolean useCertificatePayload = true; + @Path("replace_username_spaces") @Comment("Replaces username spaces with underscores if enabled") private boolean replaceUsernameSpaces = false;