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;