Search in sources :

Example 1 with LanternServer

use of org.lanternpowered.server.LanternServer in project LanternServer by LanternPowered.

the class QueryHandler method handleFullStats.

private void handleFullStats(ChannelHandlerContext ctx, DatagramPacket packet, int sessionId) {
    final LanternGame game = this.queryServer.getGame();
    final LanternServer server = game.getServer();
    final Platform platform = game.getPlatform();
    final PluginContainer api = platform.getContainer(Platform.Component.API);
    final PluginContainer impl = platform.getContainer(Platform.Component.IMPLEMENTATION);
    final PluginContainer mc = platform.getContainer(Platform.Component.GAME);
    final StringBuilder plugins = new StringBuilder().append(impl.getName()).append(" ").append(impl.getVersion()).append(" on ").append(api.getName()).append(" ").append(api.getVersion());
    if (this.showPlugins) {
        final List<PluginContainer> containers = new ArrayList<>(game.getPluginManager().getPlugins());
        containers.remove(api);
        containers.remove(impl);
        containers.remove(mc);
        char delim = ':';
        for (PluginContainer plugin : containers) {
            plugins.append(delim).append(' ').append(plugin.getName());
            delim = ';';
        }
    }
    final List<String> playerNames = server.getOnlinePlayers().stream().map(CommandSource::getName).collect(Collectors.toList());
    final Cause cause = Cause.of(EventContext.empty(), new SimpleRemoteConnection((InetSocketAddress) ctx.channel().remoteAddress(), null));
    final QueryServerEvent.Full event = SpongeEventFactory.createQueryServerEventFull(cause, (InetSocketAddress) ctx.channel().localAddress(), new HashMap<>(), "MINECRAFT", "SMP", getWorldName(), server.getMotd().toPlain(), playerNames, plugins.toString(), mc.getVersion().orElse("unknown"), server.getMaxPlayers(), Integer.MAX_VALUE, playerNames.size(), 0);
    final InetSocketAddress address = event.getAddress();
    final Map<String, Object> data = new LinkedHashMap<>();
    data.put("hostname", event.getMotd());
    data.put("gametype", event.getGameType());
    data.put("game_id", event.getGameId());
    data.put("version", event.getVersion());
    data.put("plugins", event.getPlugins());
    data.put("map", event.getMap());
    data.put("numplayers", event.getPlayerCount());
    data.put("maxplayers", event.getMaxPlayerCount());
    data.put("hostport", address.getPort());
    data.put("hostip", address.getHostString());
    event.getCustomValuesMap().entrySet().stream().filter(entry -> !data.containsKey(entry.getKey())).forEach(entry -> data.put(entry.getKey(), entry.getValue()));
    final ByteBuf buf = ctx.alloc().buffer();
    buf.writeByte(ACTION_STATS);
    buf.writeInt(sessionId);
    // constant: splitnum\x00\x80\x00
    buf.writeBytes(new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00 });
    for (Entry<String, Object> e : data.entrySet()) {
        writeString(buf, e.getKey());
        writeString(buf, String.valueOf(e.getValue()));
    }
    buf.writeByte(0);
    // constant: \x01player_\x00\x00
    buf.writeBytes(new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 });
    for (Player player : game.getServer().getOnlinePlayers()) {
        writeString(buf, player.getName());
    }
    buf.writeByte(0);
    ctx.write(new DatagramPacket(buf, packet.sender()));
}
Also used : HashMap(java.util.HashMap) Platform(org.spongepowered.api.Platform) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) ChannelHandlerContext(io.netty.channel.ChannelHandlerContext) ByteBuf(io.netty.buffer.ByteBuf) Map(java.util.Map) DatagramPacket(io.netty.channel.socket.DatagramPacket) PluginContainer(org.spongepowered.api.plugin.PluginContainer) SimpleRemoteConnection(org.lanternpowered.server.network.SimpleRemoteConnection) LanternServer(org.lanternpowered.server.LanternServer) CommandSource(org.spongepowered.api.command.CommandSource) SpongeEventFactory(org.spongepowered.api.event.SpongeEventFactory) Collection(java.util.Collection) Sponge(org.spongepowered.api.Sponge) InetSocketAddress(java.net.InetSocketAddress) Collectors(java.util.stream.Collectors) LanternGame(org.lanternpowered.server.game.LanternGame) StandardCharsets(java.nio.charset.StandardCharsets) Cause(org.spongepowered.api.event.cause.Cause) List(java.util.List) QueryServerEvent(org.spongepowered.api.event.server.query.QueryServerEvent) SimpleChannelInboundHandler(io.netty.channel.SimpleChannelInboundHandler) World(org.spongepowered.api.world.World) Entry(java.util.Map.Entry) Player(org.spongepowered.api.entity.living.player.Player) EventContext(org.spongepowered.api.event.cause.EventContext) Player(org.spongepowered.api.entity.living.player.Player) PluginContainer(org.spongepowered.api.plugin.PluginContainer) Platform(org.spongepowered.api.Platform) SimpleRemoteConnection(org.lanternpowered.server.network.SimpleRemoteConnection) InetSocketAddress(java.net.InetSocketAddress) ArrayList(java.util.ArrayList) QueryServerEvent(org.spongepowered.api.event.server.query.QueryServerEvent) ByteBuf(io.netty.buffer.ByteBuf) LanternGame(org.lanternpowered.server.game.LanternGame) LinkedHashMap(java.util.LinkedHashMap) LanternServer(org.lanternpowered.server.LanternServer) Cause(org.spongepowered.api.event.cause.Cause) DatagramPacket(io.netty.channel.socket.DatagramPacket)

Example 2 with LanternServer

use of org.lanternpowered.server.LanternServer in project LanternServer by LanternPowered.

the class HandlerStatusRequest method handle.

@Override
public void handle(NetworkContext context, MessageStatusInRequest message) {
    final NetworkSession session = context.getSession();
    final LanternServer server = session.getServer();
    final Gson gson = new Gson();
    final Text description = server.getMotd();
    final InetSocketAddress address = session.getAddress();
    final InetSocketAddress virtualAddress = session.getVirtualHost();
    final int protocol = session.getProtocolVersion();
    final MinecraftVersion clientVersion = Lantern.getGame().getMinecraftVersionCache().getVersionOrUnknown(protocol, false);
    if (clientVersion == LanternMinecraftVersion.UNKNOWN) {
        Lantern.getLogger().debug("Client with unknown protocol version {} pinged the server.", protocol);
    }
    final LanternStatusClient client = new LanternStatusClient(address, clientVersion, virtualAddress);
    final ClientPingServerEvent.Response.Players players = LanternStatusHelper.createPlayers(server);
    final LanternStatusResponse response = new LanternStatusResponse(Lantern.getGame().getPlatform().getMinecraftVersion(), server.getFavicon(), description, players);
    final Cause cause = Cause.of(EventContext.empty(), new WrappedRemoteConnection(session));
    final ClientPingServerEvent event = SpongeEventFactory.createClientPingServerEvent(cause, client, response);
    Sponge.getEventManager().post(event);
    // Cancelled, we are done here
    if (event.isCancelled()) {
        context.getChannel().close();
        return;
    }
    final JsonObject rootObject = new JsonObject();
    final JsonObject versionObject = new JsonObject();
    checkState(response.getVersion() instanceof LanternMinecraftVersion);
    final LanternMinecraftVersion serverVersion = (LanternMinecraftVersion) response.getVersion();
    versionObject.addProperty("name", serverVersion.getName());
    versionObject.addProperty("protocol", serverVersion.getProtocol());
    if (response.getPlayers().isPresent()) {
        final JsonObject playersObject = new JsonObject();
        playersObject.addProperty("max", players.getMax());
        playersObject.addProperty("online", players.getOnline());
        List<GameProfile> profiles = players.getProfiles();
        if (!profiles.isEmpty()) {
            final JsonArray array = new JsonArray();
            for (GameProfile profile : profiles) {
                Optional<String> optName = profile.getName();
                if (!optName.isPresent()) {
                    continue;
                }
                final JsonObject profileObject = new JsonObject();
                profileObject.addProperty("name", optName.get());
                profileObject.addProperty("id", profile.getUniqueId().toString());
                array.add(profileObject);
            }
            playersObject.add("sample", array);
        }
        rootObject.add("players", playersObject);
    }
    rootObject.add("version", versionObject);
    rootObject.add("description", ((LanternJsonTextSerializer) TextSerializers.JSON).getGson().toJsonTree(response.getDescription()));
    response.getFavicon().ifPresent(icon -> rootObject.addProperty("favicon", ((LanternFavicon) icon).getEncoded()));
    final JsonObject fmlObject = new JsonObject();
    // Trick the client that the server is fml, we support fml channels anyway
    fmlObject.addProperty("type", "FML");
    // The client shouldn't know the plugins (mods) list
    fmlObject.add("modList", new JsonArray());
    // Add the fml info
    rootObject.add("modinfo", fmlObject);
    session.send(new MessageStatusOutResponse(gson.toJson(rootObject)));
}
Also used : NetworkSession(org.lanternpowered.server.network.NetworkSession) MessageStatusOutResponse(org.lanternpowered.server.network.vanilla.message.type.status.MessageStatusOutResponse) LanternMinecraftVersion(org.lanternpowered.server.game.version.LanternMinecraftVersion) InetSocketAddress(java.net.InetSocketAddress) Gson(com.google.gson.Gson) JsonObject(com.google.gson.JsonObject) Text(org.spongepowered.api.text.Text) LanternJsonTextSerializer(org.lanternpowered.server.text.gson.LanternJsonTextSerializer) LanternStatusResponse(org.lanternpowered.server.network.status.LanternStatusResponse) MessageStatusOutResponse(org.lanternpowered.server.network.vanilla.message.type.status.MessageStatusOutResponse) JsonArray(com.google.gson.JsonArray) LanternStatusClient(org.lanternpowered.server.network.status.LanternStatusClient) ClientPingServerEvent(org.spongepowered.api.event.server.ClientPingServerEvent) LanternServer(org.lanternpowered.server.LanternServer) GameProfile(org.spongepowered.api.profile.GameProfile) Cause(org.spongepowered.api.event.cause.Cause) LanternFavicon(org.lanternpowered.server.network.status.LanternFavicon) LanternStatusResponse(org.lanternpowered.server.network.status.LanternStatusResponse) LanternMinecraftVersion(org.lanternpowered.server.game.version.LanternMinecraftVersion) MinecraftVersion(org.spongepowered.api.MinecraftVersion) WrappedRemoteConnection(org.lanternpowered.server.network.WrappedRemoteConnection)

Example 3 with LanternServer

use of org.lanternpowered.server.LanternServer in project LanternServer by LanternPowered.

the class CommandListPlayers method completeSpec.

@Override
public void completeSpec(PluginContainer pluginContainer, CommandSpec.Builder specBuilder) {
    specBuilder.arguments(GenericArguments.optional(GenericArguments.string(Text.of("uuids")))).executor((src, args) -> {
        final boolean includeUUIDs = "uuids".equalsIgnoreCase(args.<String>getOne("uuids").orElse(null));
        final LanternServer server = Lantern.getServer();
        src.sendMessage(t("commands.players.list", server.getRawOnlinePlayers().size(), server.getMaxPlayers()));
        src.sendMessage(Text.of(Joiner.on(", ").join(server.getRawOnlinePlayers().stream().map(player -> {
            String name = player.getName();
            if (includeUUIDs) {
                name = String.format("%s (%s)", name, player.getUniqueId().toString());
            }
            return name;
        }).collect(Collectors.toList()))));
        return CommandResult.builder().queryResult(server.getRawOnlinePlayers().size()).build();
    });
}
Also used : LanternServer(org.lanternpowered.server.LanternServer)

Example 4 with LanternServer

use of org.lanternpowered.server.LanternServer in project LanternServer by LanternPowered.

the class LegacyProtocolHandler method channelRead.

@Override
public void channelRead(ChannelHandlerContext ctx, Object object) throws Exception {
    final LanternServer server = this.session.getServer();
    final ByteBuf buf = (ByteBuf) object;
    buf.markReaderIndex();
    // Whether it was a valid legacy message
    boolean legacy = false;
    try {
        final int messageId = buf.readUnsignedByte();
        // they don't attempt to login
        if (messageId == 0x02) {
            // Protocol version
            int protocol = buf.readByte();
            int value = buf.readShort();
            // Check the length
            if (value < 0 || value > 16) {
                return;
            }
            // Username
            buf.readBytes(value << 1);
            value = buf.readShort();
            // Check the length
            if (value < 0 || value > 255) {
                return;
            }
            // Host address
            buf.readBytes(value << 1);
            // Port
            buf.readInt();
            if (buf.readableBytes() > 0) {
                return;
            }
            legacy = true;
            sendDisconnectMessage(ctx, LanternTexts.toPlain(t("multiplayer.disconnect.outdated_client", Lantern.getGame().getPlatform().getMinecraftVersion().getName())));
            final MinecraftVersion clientVersion = Lantern.getGame().getMinecraftVersionCache().getVersionOrUnknown(protocol, true);
            if (clientVersion == LanternMinecraftVersion.UNKNOWN_LEGACY) {
                Lantern.getLogger().debug("Client with unknown legacy protocol version {} attempted to join the server.", protocol);
            } else {
                Lantern.getLogger().debug("Client with legacy protocol version {} (mc-version {}) attempted to join the server.", protocol, clientVersion.getName());
            }
            return;
        }
        // Check for the ping message id.
        if (messageId != 0xfe) {
            return;
        }
        int readable = buf.readableBytes();
        boolean full = false;
        // The version used to ping the server
        int protocol = V1_3_2_PROTOCOL;
        // Versions 1.4 - 1.5.x + 1.6 - Can request full data.
        if (readable > 0) {
            // Is always 1
            if (buf.readUnsignedByte() != 1) {
                return;
            }
            full = true;
            protocol = V1_5_2_PROTOCOL;
        }
        // The virtual address that was used to join the server
        InetSocketAddress virtualAddress = null;
        // Version 1.6 - Used extra data.
        if (readable > 1) {
            if (buf.readUnsignedByte() != 0xfa) {
                return;
            }
            byte[] bytes = new byte[buf.readShort() << 1];
            buf.readBytes(bytes);
            if (!new String(bytes, StandardCharsets.UTF_16BE).equals("MC|PingHost")) {
                return;
            }
            // Not used
            buf.readShort();
            // The protocol version is present
            protocol = buf.readUnsignedByte();
            // There is extra host and port data
            if (protocol >= 73) {
                bytes = new byte[buf.readShort() << 1];
                buf.readBytes(bytes);
                final String host = new String(bytes, StandardCharsets.UTF_16BE);
                final int port = buf.readInt();
                virtualAddress = InetSocketAddress.createUnresolved(host, port);
            }
            readable = buf.readableBytes();
            if (readable > 0) {
                Lantern.getLogger().warn("Trailing bytes on a legacy ping message: {}b", readable);
            }
        }
        // The message was successfully decoded as a legacy one
        legacy = true;
        final boolean full1 = full;
        final int protocol1 = protocol;
        final InetSocketAddress virtualAddress1 = virtualAddress;
        // Call the event in the main thread
        Lantern.getScheduler().callSync(() -> {
            final MinecraftVersion clientVersion = Lantern.getGame().getMinecraftVersionCache().getVersionOrUnknown(protocol1, true);
            if (clientVersion == LanternMinecraftVersion.UNKNOWN) {
                Lantern.getLogger().debug("Client with unknown legacy protocol version {} pinged the server.", protocol1);
            }
            final MinecraftVersion serverVersion = Lantern.getGame().getPlatform().getMinecraftVersion();
            Text description = server.getMotd();
            final InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
            final LanternStatusClient client = new LanternStatusClient(address, clientVersion, virtualAddress1);
            final ClientPingServerEvent.Response.Players players = LanternStatusHelper.createPlayers(server);
            final LanternStatusResponse response = new LanternStatusResponse(serverVersion, server.getFavicon(), description, players);
            final SimpleRemoteConnection connection = new SimpleRemoteConnection(address, virtualAddress1);
            final Cause cause = Cause.of(EventContext.empty(), connection);
            final ClientPingServerEvent event = SpongeEventFactory.createClientPingServerEvent(cause, client, response);
            Sponge.getEventManager().post(event);
            // Cancelled, we are done here
            if (event.isCancelled()) {
                ctx.channel().close();
                return;
            }
            description = response.getDescription();
            int online = players.getOnline();
            final int max = players.getMax();
            // with ???
            if (!response.getPlayers().isPresent()) {
                online = -1;
            }
            final String data;
            if (full1) {
                final String description0 = getFirstLine(TextSerializers.LEGACY_FORMATTING_CODE.serialize(description));
                // 1. This value is always 1.
                // 2. The protocol version, just use a value out of range
                // of the available ones.
                // 3. The version/name string of the server.
                // 4. The motd of the server. In legacy format.
                // 5. The online players
                // 6. The maximum amount of players
                data = String.format("\u00A7%s\u0000%s\u0000%s\u0000%s\u0000%s\u0000%s", 1, 127, response.getVersion().getName(), description0, online, max);
            } else {
                final String description0 = getFirstLine(TextSerializers.PLAIN.serialize(description));
                // 1. The motd of the server. In legacy format.
                // 2. The online players
                // 3. The maximum amount of players
                data = String.format("%s\u00A7%s\u00A7%s", description0, online, max);
            }
            sendDisconnectMessage(ctx, data);
        });
    } catch (Exception ignore) {
    } finally {
        if (legacy) {
            buf.release();
        } else {
            buf.resetReaderIndex();
            ctx.channel().pipeline().remove(this);
            ctx.fireChannelRead(buf);
        }
    }
}
Also used : SimpleRemoteConnection(org.lanternpowered.server.network.SimpleRemoteConnection) InetSocketAddress(java.net.InetSocketAddress) Text(org.spongepowered.api.text.Text) ByteBuf(io.netty.buffer.ByteBuf) LanternStatusResponse(org.lanternpowered.server.network.status.LanternStatusResponse) LanternStatusClient(org.lanternpowered.server.network.status.LanternStatusClient) ClientPingServerEvent(org.spongepowered.api.event.server.ClientPingServerEvent) LanternServer(org.lanternpowered.server.LanternServer) Cause(org.spongepowered.api.event.cause.Cause) LanternStatusResponse(org.lanternpowered.server.network.status.LanternStatusResponse) LanternMinecraftVersion(org.lanternpowered.server.game.version.LanternMinecraftVersion) MinecraftVersion(org.spongepowered.api.MinecraftVersion)

Example 5 with LanternServer

use of org.lanternpowered.server.LanternServer in project LanternServer by LanternPowered.

the class QueryHandler method handleBasicStats.

private void handleBasicStats(ChannelHandlerContext ctx, DatagramPacket packet, int sessionId) {
    LanternServer server = this.queryServer.getGame().getServer();
    // TODO: Find out how to support the size and max size properties
    final Cause cause = Cause.of(EventContext.empty(), new SimpleRemoteConnection((InetSocketAddress) ctx.channel().remoteAddress(), null));
    final QueryServerEvent.Basic event = SpongeEventFactory.createQueryServerEventBasic(cause, (InetSocketAddress) ctx.channel().localAddress(), "SMP", this.getWorldName(), server.getMotd().toPlain(), server.getMaxPlayers(), Integer.MAX_VALUE, server.getOnlinePlayers().size(), 0);
    Sponge.getEventManager().post(event);
    final InetSocketAddress address = event.getAddress();
    final ByteBuf buf = ctx.alloc().buffer();
    buf.writeByte(ACTION_STATS);
    buf.writeInt(sessionId);
    writeString(buf, event.getMotd());
    writeString(buf, event.getGameType());
    writeString(buf, event.getMap());
    writeString(buf, String.valueOf(event.getPlayerCount()));
    writeString(buf, String.valueOf(event.getMaxPlayerCount()));
    buf.writeShortLE(address.getPort());
    writeString(buf, address.getHostString());
    ctx.write(new DatagramPacket(buf, packet.sender()));
}
Also used : LanternServer(org.lanternpowered.server.LanternServer) SimpleRemoteConnection(org.lanternpowered.server.network.SimpleRemoteConnection) InetSocketAddress(java.net.InetSocketAddress) Cause(org.spongepowered.api.event.cause.Cause) DatagramPacket(io.netty.channel.socket.DatagramPacket) QueryServerEvent(org.spongepowered.api.event.server.query.QueryServerEvent) ByteBuf(io.netty.buffer.ByteBuf)

Aggregations

LanternServer (org.lanternpowered.server.LanternServer)5 InetSocketAddress (java.net.InetSocketAddress)4 Cause (org.spongepowered.api.event.cause.Cause)4 ByteBuf (io.netty.buffer.ByteBuf)3 SimpleRemoteConnection (org.lanternpowered.server.network.SimpleRemoteConnection)3 DatagramPacket (io.netty.channel.socket.DatagramPacket)2 LanternMinecraftVersion (org.lanternpowered.server.game.version.LanternMinecraftVersion)2 LanternStatusClient (org.lanternpowered.server.network.status.LanternStatusClient)2 LanternStatusResponse (org.lanternpowered.server.network.status.LanternStatusResponse)2 MinecraftVersion (org.spongepowered.api.MinecraftVersion)2 ClientPingServerEvent (org.spongepowered.api.event.server.ClientPingServerEvent)2 QueryServerEvent (org.spongepowered.api.event.server.query.QueryServerEvent)2 Gson (com.google.gson.Gson)1 JsonArray (com.google.gson.JsonArray)1 JsonObject (com.google.gson.JsonObject)1 ChannelHandlerContext (io.netty.channel.ChannelHandlerContext)1 SimpleChannelInboundHandler (io.netty.channel.SimpleChannelInboundHandler)1 StandardCharsets (java.nio.charset.StandardCharsets)1 ArrayList (java.util.ArrayList)1 Collection (java.util.Collection)1