From d951ee83b4d2c2f8a9035f83e46d8d2d9ed48a16 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 8 Aug 2020 16:37:01 -0500 Subject: [PATCH] Initial plugin message and forum support for GeyserAddons --- .../geysermc/connector/GeyserConnector.java | 2 + .../network/UpstreamPacketHandler.java | 7 +- .../network/addon/AddonListener.java | 60 ++++++++++ .../network/addon/AddonListenerRegistry.java | 62 +++++++++++ .../network/addon/FormAddonListener.java | 73 ++++++++++++ .../java/JavaPluginMessageTranslator.java | 23 ++++ .../connector/utils/LoginEncryptionUtils.java | 4 +- .../connector/utils/NetworkUtils.java | 104 ++++++++++++++++++ 8 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/addon/AddonListener.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/addon/AddonListenerRegistry.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/addon/FormAddonListener.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/NetworkUtils.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 6224dfcd5..b092720dd 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -40,6 +40,7 @@ import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; +import org.geysermc.connector.network.addon.AddonListenerRegistry; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; @@ -136,6 +137,7 @@ public class GeyserConnector { PotionMixRegistry.init(); SoundRegistry.init(); SoundHandlerRegistry.init(); + AddonListenerRegistry.init(); if (platformType != PlatformType.STANDALONE) { DockerCheck.check(bootstrap); diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index dd9f48d6a..3df22d3fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -30,6 +30,7 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.addon.FormAddonListener; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.utils.LoginEncryptionUtils; @@ -91,7 +92,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + if (packet.getFormId() == LoginEncryptionUtils.AUTH_FORM_ID || packet.getFormId() == LoginEncryptionUtils.AUTH_DETAILS_FORM_ID) { + return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + } + FormAddonListener.get().handleResponse(this.session, packet); + return true; } private boolean couldLoginUserByName(String bedrockUsername) { diff --git a/connector/src/main/java/org/geysermc/connector/network/addon/AddonListener.java b/connector/src/main/java/org/geysermc/connector/network/addon/AddonListener.java new file mode 100644 index 000000000..7cc1f05f2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/addon/AddonListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.addon; + +import com.nukkitx.protocol.bedrock.BedrockPacket; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.geysermc.connector.network.session.GeyserSession; + +/** + * A listener that listens for plugin messages from + * GeyserAddons. + */ +@Getter +@AllArgsConstructor +public abstract class AddonListener { + + public static final String PLUGIN_MESSAGE_CHANNEL = "geyser:addons"; + + private String subChannel; + + /** + * Called when a message is received + * @param session the session receiving the message + * @param message the message + */ + public abstract void onMessageReceive(GeyserSession session, ByteBuf message); + + /** + * Handles a response from the given packet. + * + * @param session the session listening + * @param response the response + */ + public abstract void handleResponse(GeyserSession session, T response); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/addon/AddonListenerRegistry.java b/connector/src/main/java/org/geysermc/connector/network/addon/AddonListenerRegistry.java new file mode 100644 index 000000000..4e4680a93 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/addon/AddonListenerRegistry.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.addon; + +import java.util.HashMap; +import java.util.Map; + +/** + * A registry containing all the {@link AddonListener}s listening + * for plugin messages. + */ +public class AddonListenerRegistry { + + private static final Map> LISTENERS = new HashMap<>(); + + private AddonListenerRegistry() { + } + + public static void init() { + registerListener(FormAddonListener.get()); + } + + /** + * Returns all the {@link AddonListener}s listening for + * plugin messages. + * + * Key: the subchannel the listener is listening on + * Value: the {@link AddonListener} + * + * @return all the {@link AddonListener}s listening for plugin messages. + */ + public static Map> getListeners() { + return LISTENERS; + } + + private static void registerListener(AddonListener listener) { + LISTENERS.put(listener.getSubChannel(), listener); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/addon/FormAddonListener.java b/connector/src/main/java/org/geysermc/connector/network/addon/FormAddonListener.java new file mode 100644 index 000000000..6ed058140 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/addon/FormAddonListener.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.addon; + +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; +import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.NetworkUtils; + +public class FormAddonListener extends AddonListener { + + private static final FormAddonListener INSTANCE = new FormAddonListener(); + + public static final String NAME = "form"; + + public FormAddonListener() { + super(NAME); + } + + @Override + public void onMessageReceive(GeyserSession session, ByteBuf message) { + ModalFormRequestPacket packet = new ModalFormRequestPacket(); + packet.setFormId(message.readInt()); + packet.setFormData(NetworkUtils.readString(message)); + session.sendUpstreamPacket(packet); + } + + @Override + public void handleResponse(GeyserSession session, ModalFormResponsePacket packet) { + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); + try { + NetworkUtils.writeString(buffer, this.getSubChannel()); + buffer.writeInt(packet.getFormId()); + NetworkUtils.writeString(buffer, packet.getFormData()); + + byte[] bytes = new byte[buffer.readableBytes()]; + buffer.readBytes(bytes); + session.sendDownstreamPacket(new ClientPluginMessagePacket(PLUGIN_MESSAGE_CHANNEL, bytes)); + } finally { + buffer.release(); + } + } + + public static FormAddonListener get() { + return INSTANCE; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 85c7ab13d..4954fb133 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -25,15 +25,21 @@ package org.geysermc.connector.network.translators.java; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.addon.AddonListener; +import org.geysermc.connector.network.addon.AddonListenerRegistry; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import org.geysermc.connector.utils.NetworkUtils; import java.nio.charset.StandardCharsets; +import java.util.Map; @Translator(packet = ServerPluginMessagePacket.class) public class JavaPluginMessageTranslator extends PacketTranslator { @@ -56,6 +62,23 @@ public class JavaPluginMessageTranslator extends PacketTranslator> entry : AddonListenerRegistry.getListeners().entrySet()) { + ByteBuf buffer = Unpooled.wrappedBuffer(packet.getData()); + String subChannel = NetworkUtils.readString(buffer); + if (!subChannel.equals(entry.getKey())) { + continue; + } + + entry.getValue().onMessageReceive(session, buffer); + } } private static byte[] writeVarInt(int value) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 3d4dd506a..0cd646c4c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -152,8 +152,8 @@ public class LoginEncryptionUtils { session.sendUpstreamPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1336; - private static int AUTH_DETAILS_FORM_ID = 1337; + public static int AUTH_FORM_ID = 1336; + public static int AUTH_DETAILS_FORM_ID = 1337; public static void showLoginWindow(GeyserSession session) { String userLanguage = session.getClientData().getLanguageCode(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/NetworkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/NetworkUtils.java new file mode 100644 index 000000000..2361a7bbf --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/NetworkUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import io.netty.buffer.ByteBuf; + +import java.nio.charset.StandardCharsets; + +public class NetworkUtils { + + public static String readString(ByteBuf buf) { + return new String(readByteArray(buf), StandardCharsets.UTF_8); + } + + public static void writeString(ByteBuf buf, String string) { + writeByteArray(buf, string.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] readByteArray(ByteBuf buf) { + byte[] bytes = new byte[readUnsignedVarInt(buf)]; + buf.readBytes(bytes); + return bytes; + } + + public static void writeByteArray(ByteBuf buf, byte[] bytes) { + writeUnsignedVarInt(buf, bytes.length); + buf.writeBytes(bytes); + } + + public static int readVarInt(ByteBuf buf) { + int i = 0, j = 0; + while (true) { + int k = buf.readByte(); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt was too big!"); + } + if ((k & 0x80) != 128) { + break; + } + } + return i; + } + + public static void writeVarInt(ByteBuf buf, int value) { + while (true) { + if ((value & 0xFFFFFF80) == 0) { + buf.writeByte(value); + return; + } + + buf.writeByte(value & 0x7F | 0x80); + value >>>= 7; + } + } + + public static int readUnsignedVarInt(ByteBuf buf) { + int i = 0; + for (int j = 0; j < 64; j += 7) { + final byte b = buf.readByte(); + i |= (long) (b & 0x7F) << j; + if ((b & 0x80) == 0) { + return i; + } + } + throw new RuntimeException("Varint was too big!"); + } + + public static void writeUnsignedVarInt(ByteBuf buf, int value) { + long i = value & 0xFFFFFFFFL; + while (true) { + if ((i & ~0x7FL) == 0) { + buf.writeByte((int) i); + return; + } else { + buf.writeByte((byte) ((i & 0x7F) | 0x80)); + i >>>= 7; + } + } + } +}