Rewrite to move to a Geyser extension

This commit is contained in:
rtm516 2023-04-25 22:07:05 +01:00
commit 91bc9657a4
No known key found for this signature in database
GPG key ID: 331715B8B007C67A
49 changed files with 1350 additions and 2796 deletions

View file

@ -0,0 +1,61 @@
package org.geysermc.connect.extension;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketHandler;
import org.geysermc.connect.extension.config.Config;
import org.geysermc.connect.extension.config.ConfigLoader;
import org.geysermc.connect.extension.storage.AbstractStorageManager;
import org.geysermc.connect.extension.storage.DisabledStorageManager;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.session.GeyserSession;
public class GeyserConnect implements Extension {
private static GeyserConnect instance;
private Config config;
private AbstractStorageManager storageManager;
public GeyserConnect() {
instance = this;
}
public static GeyserConnect instance() {
return instance;
}
public Config config() {
return config;
}
public AbstractStorageManager storageManager() {
return storageManager;
}
@Subscribe
public void onPostInitialize(GeyserPostInitializeEvent event) {
config = ConfigLoader.load(this, GeyserConnect.class, Config.class);
if (!config.customServers().enabled()) {
// Force the storage manager if we have it disabled
storageManager = new DisabledStorageManager();
this.logger().info("Disabled custom player servers");
} else {
try {
storageManager = config.customServers().storageType().storageManager().getDeclaredConstructor().newInstance();
} catch (Exception e) {
this.logger().severe("Invalid storage manager class!", e);
return;
}
}
storageManager.setupStorage();
}
@Subscribe
public void onSessionInitialize(SessionInitializeEvent event) {
GeyserSession session = (GeyserSession) event.connection();
BedrockPacketHandler packetHandler = session.getUpstream().getSession().getPacketHandler();
session.getUpstream().getSession().setPacketHandler(new PacketHandler(this, session, packetHandler));
}
}

View file

@ -0,0 +1,142 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketHandler;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.cloudburstmc.protocol.common.PacketSignal;
import org.geysermc.connect.extension.ui.UIHandler;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.network.UpstreamPacketHandler;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.DimensionUtils;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class PacketHandler extends UpstreamPacketHandler {
private final GeyserSession session;
private final GeyserConnect geyserConnect;
private final BedrockPacketHandler originalPacketHandler;
public PacketHandler(GeyserConnect geyserConnect, GeyserSession session, BedrockPacketHandler packetHandler) {
super(GeyserImpl.getInstance(), session);
this.session = session;
this.geyserConnect = geyserConnect;
this.originalPacketHandler = packetHandler;
// Spawn the player in the end (it just looks better)
session.setDimension(DimensionUtils.THE_END);
DimensionUtils.setBedrockDimension(session, DimensionUtils.THE_END);
}
@Override
public void onDisconnect(String reason) {
geyserConnect.logger().info(Utils.displayName(session) + " has disconnected (" + reason + ")");
ServerManager.unloadServers(session);
}
@Override
public PacketSignal handle(SetLocalPlayerAsInitializedPacket packet) {
if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) {
if (!session.getUpstream().isInitialized()) {
session.getUpstream().setInitialized(true);
// Load the players servers
ServerManager.loadServers(session);
geyserConnect.logger().debug("Player initialized: " + Utils.displayName(session));
UIHandler uiHandler = new UIHandler(session, packet, originalPacketHandler);
uiHandler.initialiseSession();
}
}
// Handle the virtual host if specified
// GeyserConnectConfig.VirtualHostSection vhost = MasterServer.getInstance().getGeyserConnectConfig().getVhost();
// if (vhost.isEnabled()) {
// String domain = player.getClientData().getServerAddress().split(":")[0];
// if (!domain.equals(vhost.getBaseDomain()) && domain.endsWith("." + vhost.getBaseDomain())) {
// String address = "";
// int port = 25565;
// boolean online = true;
//
// // Parse the address used
// String[] domainParts = domain.replaceFirst("\\." + vhost.getBaseDomain() + "$", "").split("\\._");
// for (int i = 0; i < domainParts.length; i++) {
// String part = domainParts[i];
// if (i == 0) {
// address = part;
// } else if (part.startsWith("p")) {
// port = Integer.parseInt(part.substring(1));
// } else if (part.startsWith("o")) {
// online = false;
// }
// }
//
// // They didn't specify an address so disconnect them
// if (address.startsWith("_")) {
// session.disconnect("disconnectionScreen.invalidIP");
// return false;
// }
//
// // Log the virtual host usage
// masterServer.getLogger().info(player.getAuthData().name() + " is using virtualhost: " + address + ":" + port + (!online ? " (offline)" : ""));
//
// // Send the player to the wanted server
// player.sendToServer(new Server(address, port, online, false));
//
// return false;
// }
// }
return PacketSignal.HANDLED;
}
@Override
public PacketSignal handle(NetworkStackLatencyPacket packet) {
// This is to fix a bug in the client where it doesn't load form images
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
updateAttributesPacket.setRuntimeEntityId(1);
List<AttributeData> attributes = Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0f));
updateAttributesPacket.setAttributes(attributes);
// Doesn't work 100% of the time but fixes it most of the time
GeyserImpl.getInstance().getScheduledThread().schedule(() -> session.sendUpstreamPacket(updateAttributesPacket), 500, TimeUnit.MILLISECONDS);
return super.handle(packet);
}
}

View file

@ -0,0 +1,38 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.geysermc.connect.extension.utils.Server;
import java.util.List;
public record Config(
@JsonProperty("welcome-file") String welcomeFile,
List<Server> servers,
@JsonProperty("custom-servers") CustomServersSection customServers,
VirtualHostSection vhost) {
}

View file

@ -0,0 +1,60 @@
package org.geysermc.connect.extension.config;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.geysermc.geyser.api.extension.Extension;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
public class ConfigLoader {
public static <T> T load(Extension extension, Class<?> extensionClass, Class<T> configClass) {
File configFile = extension.dataFolder().resolve("config.yml").toFile();
// Ensure the data folder exists
if (!extension.dataFolder().toFile().exists()) {
if (!extension.dataFolder().toFile().mkdirs()) {
extension.logger().error("Failed to create data folder");
return null;
}
}
// Create the config file if it doesn't exist
if (!configFile.exists()) {
try (FileWriter writer = new FileWriter(configFile)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(new File(extensionClass.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath(), Collections.emptyMap())) {
try (InputStream input = Files.newInputStream(fileSystem.getPath("config.yml"))) {
byte[] bytes = new byte[input.available()];
input.read(bytes);
writer.write(new String(bytes).toCharArray());
writer.flush();
}
}
} catch (IOException | URISyntaxException e) {
extension.logger().error("Failed to create config", e);
return null;
}
}
// Load the config file
try {
return new ObjectMapper(new YAMLFactory())
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.readValue(configFile, configClass);
} catch (IOException e) {
extension.logger().error("Failed to load config", e);
return null;
}
}
}

View file

@ -0,0 +1,14 @@
package org.geysermc.connect.extension.config;
//import com.fasterxml.jackson.annotation.JsonProperty;
//import org.geysermc.connect.extension.storage.AbstractStorageManager;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.geysermc.connect.extension.storage.AbstractStorageManager;
public record CustomServersSection(
boolean enabled,
int max,
@JsonProperty("storage-type") AbstractStorageManager.StorageType storageType,
MySQLConnectionSection mysql) {
}

View file

@ -0,0 +1,9 @@
package org.geysermc.connect.extension.config;
public record MySQLConnectionSection(
String user,
String pass,
String database,
String host,
int port) {
}

View file

@ -0,0 +1,8 @@
package org.geysermc.connect.extension.config;
import com.fasterxml.jackson.annotation.JsonProperty;
public record VirtualHostSection(
boolean enabled,
@JsonProperty("base-domain") String baseDomain) {
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2019-2022 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.geyser.session.GeyserSession;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
protected Connection connection;
@Override
public void setupStorage() {
try {
connectToDatabase();
try (Statement createPlayersTable = connection.createStatement()) {
createPlayersTable.executeUpdate("CREATE TABLE IF NOT EXISTS players (xuid VARCHAR(32), servers TEXT, PRIMARY KEY(xuid));");
}
try (PreparedStatement getPlayersServers = connection.prepareStatement("SELECT xuid, servers FROM players")) {
ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) {
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(rs.getString("servers"), new TypeReference<>() {});
GeyserConnect.instance().logger().info("Loaded " + loadedServers.size() + " servers for " + rs.getString("xuid"));
}
} catch (IOException | SQLException exception) {
GeyserConnect.instance().logger().error("Couldn't load servers", exception);
}
} catch (ClassNotFoundException | SQLException e) {
GeyserConnect.instance().logger().severe("Unable to connect to MySQL database!", e);
}
}
protected abstract void connectToDatabase() throws ClassNotFoundException, SQLException;
@Override
public void closeStorage() {
try {
connection.close();
} catch (SQLException exception) {
GeyserConnect.instance().logger().error("Failed to close SQL connection", exception);
}
}
@Override
public void saveServers(GeyserSession session) {
// replace into works on MySQL and SQLite
try (PreparedStatement updatePlayersServers = connection.prepareStatement("REPLACE INTO players(xuid, servers) VALUES(?, ?)")) {
updatePlayersServers.setString(1, session.getAuthData().xuid());
updatePlayersServers.setString(2, Utils.OBJECT_MAPPER.writeValueAsString(ServerManager.getServers(session)));
updatePlayersServers.executeUpdate();
} catch (IOException | SQLException exception) {
GeyserConnect.instance().logger().error("Couldn't save servers for " + session.getAuthData().name(), exception);
}
}
@Override
public List<Server> loadServers(GeyserSession session) {
List<Server> servers = new ArrayList<>();
try (PreparedStatement getPlayersServers = connection.prepareStatement("SELECT servers FROM players WHERE xuid=?")) {
getPlayersServers.setString(1, session.getAuthData().xuid());
ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) {
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(rs.getString("servers"), new TypeReference<>() {});
if (loadedServers != null) {
servers.addAll(loadedServers);
}
}
} catch (IOException | SQLException exception) {
GeyserConnect.instance().logger().error("Couldn't load servers for " + session.getAuthData().name(), exception);
}
return servers;
}
}

View file

@ -0,0 +1,71 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.annotation.JsonValue;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.geyser.session.GeyserSession;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class AbstractStorageManager {
public void setupStorage() { }
public void closeStorage() { }
public void saveServers(GeyserSession session) { }
public List<Server> loadServers(GeyserSession session) {
return new ArrayList<>();
}
public enum StorageType {
JSON("json", JsonStorageManager.class),
SQLITE("sqlite", SQLiteStorageManager.class),
MYSQL("mysql", MySQLStorageManager.class);
@JsonValue
private final String configName;
private final Class<? extends AbstractStorageManager> storageManager;
StorageType(String configName, Class<? extends AbstractStorageManager> storageManager) {
this.configName = configName;
this.storageManager = storageManager;
}
public String configName() {
return configName;
}
public Class<? extends AbstractStorageManager> storageManager() {
return storageManager;
}
}
}

View file

@ -0,0 +1,49 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.geyser.session.GeyserSession;
import java.util.ArrayList;
import java.util.List;
public class DisabledStorageManager extends AbstractStorageManager {
@Override
public void setupStorage() {
}
@Override
public void saveServers(GeyserSession session) {
}
@Override
public List<Server> loadServers(GeyserSession session) {
return new ArrayList<>();
}
}

View file

@ -0,0 +1,72 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.geyser.session.GeyserSession;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class JsonStorageManager extends AbstractStorageManager {
private Path dataFolder;
@Override
public void setupStorage() {
dataFolder = GeyserConnect.instance().dataFolder().resolve("players/");
if (!dataFolder.toFile().exists()) {
dataFolder.toFile().mkdirs();
}
}
@Override
public void saveServers(GeyserSession session) {
try {
Utils.OBJECT_MAPPER.writeValue(dataFolder.resolve(session.getAuthData().xuid() + ".json").toFile(), ServerManager.getServers(session));
} catch (IOException ignored) { }
}
@Override
public List<Server> loadServers(GeyserSession session) {
List<Server> servers = new ArrayList<>();
try {
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(dataFolder.resolve(session.getAuthData().xuid() + ".json").toFile(), new TypeReference<>(){});
if (loadedServers != null) {
servers.addAll(loadedServers);
}
} catch (IOException ignored) { }
return servers;
}
}

View file

@ -0,0 +1,41 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.config.MySQLConnectionSection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MySQLStorageManager extends AbstractSQLStorageManager {
@Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
MySQLConnectionSection connectionInformation = GeyserConnect.instance().config().customServers().mysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.host() + ":" + connectionInformation.port() + "/" + connectionInformation.database(), connectionInformation.user(), connectionInformation.pass());
}
}

View file

@ -0,0 +1,39 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.extension.GeyserConnect;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLiteStorageManager extends AbstractSQLStorageManager {
@Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + GeyserConnect.instance().dataFolder().resolve("players.db"));
}
}

View file

@ -0,0 +1,314 @@
package org.geysermc.connect.extension.ui;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketHandler;
import org.cloudburstmc.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.cloudburstmc.protocol.bedrock.packet.TransferPacket;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.connect.extension.utils.ServerCategory;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.cumulus.form.CustomForm;
import org.geysermc.cumulus.form.ModalForm;
import org.geysermc.cumulus.form.SimpleForm;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class UIHandler {
private final GeyserSession session;
private final SetLocalPlayerAsInitializedPacket initializedPacket;
private final BedrockPacketHandler originalPacketHandler;
public UIHandler(GeyserSession session, SetLocalPlayerAsInitializedPacket packet, BedrockPacketHandler originalPacketHandler) {
this.session = session;
this.initializedPacket = new SetLocalPlayerAsInitializedPacket();
this.initializedPacket.setRuntimeEntityId(packet.getRuntimeEntityId());
this.originalPacketHandler = originalPacketHandler;
}
private void sendToServer(Server server) {
GeyserConnect.instance().logger().info("Sending " + Utils.displayName(session) + " to " + server.title());
GeyserConnect.instance().logger().debug(server.toString());
if (server.bedrock()) {
// Send them to the bedrock server
TransferPacket transferPacket = new TransferPacket();
transferPacket.setAddress(server.address());
transferPacket.setPort(server.port());
session.sendUpstreamPacket(transferPacket);
} else {
// Save the players servers since we are changing packet handlers
ServerManager.unloadServers(session);
// Restore the original packet handler
session.getUpstream().getSession().setPacketHandler(originalPacketHandler);
// Set the remote server and un-initialize the session
session.remoteServer(server);
session.getUpstream().setInitialized(false);
// Hand back to core geyser
originalPacketHandler.handle(initializedPacket);
}
}
public void initialiseSession() {
String message = "";
try {
File messageFile = Utils.fileOrCopiedFromResource(GeyserConnect.instance().config().welcomeFile(), "welcome.txt");
message = new String(FileUtils.readAllBytes(messageFile), StandardCharsets.UTF_8);
} catch (IOException ignored) { }
if (!message.trim().isEmpty()) {
session.sendForm(CustomForm.builder()
.title("Notice")
.label(message)
.resultHandler((customForm, customFormResponseFormResponseResult) -> {
sendMainMenu();
// this.sendToServer(new Server("test.geysermc.org", 25565));
})
.build());
} else {
sendMainMenu();
}
}
public void sendMainMenu() {
SimpleForm.Builder mainMenu = SimpleForm.builder()
.title("Main Menu")
.button("Official Servers")
.button("Geyser Servers");
// Add a buttons for custom servers
if (GeyserConnect.instance().config().customServers().enabled()) {
mainMenu.button("Custom Servers");
mainMenu.button("Direct connect");
}
mainMenu
.button("Disconnect")
.closedResultHandler(response -> {
sendMainMenu();
})
.invalidResultHandler(response -> {
session.disconnect("disconnectionScreen.disconnected");
})
.validResultHandler(response -> {
switch (response.clickedButtonId()) {
case 0:
sendServersMenu(ServerCategory.OFFICIAL);
return;
case 1:
sendServersMenu(ServerCategory.GEYSER);
return;
default:
if (GeyserConnect.instance().config().customServers().enabled()) {
switch (response.clickedButtonId()) {
case 2:
sendServersMenu(ServerCategory.CUSTOM);
return;
case 3:
sendDirectConnectMenu();
return;
}
}
break;
}
session.disconnect("disconnectionScreen.disconnected");
});
session.sendForm(mainMenu);
}
public void sendServersMenu(ServerCategory category) {
SimpleForm.Builder serversMenu = SimpleForm.builder()
.title(category.title() + " Servers");
List<Server> servers;
if (category == ServerCategory.CUSTOM) {
servers = ServerManager.getServers(session);
} else {
servers = Utils.getServers(category);
}
for (Server server : servers) {
serversMenu.button(server.title(), server.formImage());
}
if (category == ServerCategory.CUSTOM) {
serversMenu.button("Edit servers");
}
serversMenu
.button("Back")
.closedOrInvalidResultHandler(response -> {
sendMainMenu();
})
.validResultHandler(response -> {
if (category == ServerCategory.CUSTOM) {
if (response.clickedButtonId() == servers.size()) {
sendEditServersMenu();
return;
} else if (response.clickedButtonId() == servers.size() + 1) {
sendMainMenu();
return;
}
} else if (response.clickedButtonId() == servers.size()) {
sendMainMenu();
return;
}
Server server = servers.get(response.clickedButtonId());
sendToServer(server);
});
session.sendForm(serversMenu);
}
public void sendEditServersMenu() {
SimpleForm.Builder editServersMenu = SimpleForm.builder()
.title("Edit Servers")
.content("Select a server to edit");
List<Server> servers = ServerManager.getServers(session);
for (Server server : servers) {
editServersMenu.button(server.title(), server.formImage());
}
editServersMenu
.button("Add server")
.button("Back")
.closedOrInvalidResultHandler(response -> {
sendServersMenu(ServerCategory.CUSTOM);
})
.validResultHandler(response -> {
if (response.clickedButtonId() == servers.size()) {
sendAddServerMenu();
return;
} else if (response.clickedButtonId() == servers.size() + 1) {
sendServersMenu(ServerCategory.CUSTOM);
return;
}
Server server = servers.get(response.clickedButtonId());
sendServerOptionsMenu(server);
});
session.sendForm(editServersMenu);
}
public void sendAddServerMenu() {
session.sendForm(CustomForm.builder()
.title("Add Server")
.input("IP", "play.cubecraft.net")
.input("Port", "25565", "25565")
.toggle("Online mode", true)
.toggle("Bedrock/Geyser server", false)
.closedOrInvalidResultHandler(response -> {
sendEditServersMenu();
})
.validResultHandler(response -> {
String ip = response.asInput(0);
int port = Integer.parseInt(response.asInput(1));
boolean onlineMode = response.asToggle(2);
boolean geyserServer = response.asToggle(3);
Server server = new Server(ip, port, onlineMode, geyserServer, null, null, ServerCategory.CUSTOM);
ServerManager.addServer(session, server);
sendEditServersMenu();
}));
}
public void sendServerOptionsMenu(Server server) {
session.sendForm(SimpleForm.builder()
.title("Server Options")
.content(server.title())
.button("Edit server")
.button("Delete server")
.button("Back")
.closedOrInvalidResultHandler(response -> {
sendEditServersMenu();
})
.validResultHandler(response -> {
switch (response.clickedButtonId()) {
case 0:
sendEditServerMenu(server);
return;
case 1:
sendDeleteServerMenu(server);
return;
case 2:
sendEditServersMenu();
return;
}
}));
}
public void sendEditServerMenu(Server server) {
int serverIndex = ServerManager.getServerIndex(session, server);
session.sendForm(CustomForm.builder()
.title("Edit Server")
.input("IP", server.address(), server.address())
.input("Port", String.valueOf(server.port()), String.valueOf(server.port()))
.toggle("Online mode", server.online())
.toggle("Bedrock/Geyser server", server.bedrock())
.closedOrInvalidResultHandler(response -> {
sendServerOptionsMenu(server);
})
.validResultHandler(response -> {
String ip = response.asInput(0);
int port = Integer.parseInt(response.asInput(1));
boolean onlineMode = response.asToggle(2);
boolean geyserServer = response.asToggle(3);
Server newServer = new Server(ip, port, onlineMode, geyserServer, null, null, ServerCategory.CUSTOM);
ServerManager.updateServer(session, serverIndex, newServer);
sendServerOptionsMenu(newServer);
}));
}
public void sendDeleteServerMenu(Server server) {
session.sendForm(ModalForm.builder()
.title("Delete Server")
.content("Are you sure you want to delete " + server.title() + "?")
.button1("Yes")
.button2("No")
.closedOrInvalidResultHandler(response -> {
sendServerOptionsMenu(server);
})
.validResultHandler(response -> {
if (response.clickedButtonId() == 0) {
ServerManager.removeServer(session, server);
}
sendEditServersMenu();
}));
}
public void sendDirectConnectMenu() {
session.sendForm(CustomForm.builder()
.title("Direct Connect")
.input("IP", "play.cubecraft.net")
.input("Port", "25565", "25565")
.toggle("Online mode", true)
.toggle("Bedrock/Geyser server", false)
.closedOrInvalidResultHandler(response -> {
sendMainMenu();
})
.validResultHandler(response -> {
String ip = response.asInput(0);
int port = Integer.parseInt(response.asInput(1));
boolean onlineMode = response.asToggle(2);
boolean geyserServer = response.asToggle(3);
Server server = new Server(ip, port, onlineMode, geyserServer, null, null, ServerCategory.CUSTOM);
sendToServer(server);
}));
}
}

View file

@ -0,0 +1,81 @@
/*
* 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/GeyserConnect
*/
package org.geysermc.connect.extension.utils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.cumulus.util.FormImage;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
public record Server(
String address,
int port,
boolean online,
boolean bedrock,
String name,
String imageUrl,
ServerCategory category
) implements RemoteServer {
public Server(String address, int port) {
this(address, port, true, false, null, null, ServerCategory.CUSTOM);
}
private int defaultPort() { return bedrock ? 19132 : 25565; }
@Override
public int port() {
return port < 0 ? defaultPort() : port;
}
@Override
public @NonNull AuthType authType() {
return this.online ? AuthType.ONLINE : AuthType.OFFLINE;
}
@JsonIgnore
public FormImage formImage() {
if (imageUrl != null && !imageUrl.isEmpty()) {
return FormImage.of(FormImage.Type.URL, imageUrl);
} else {
return FormImage.of(FormImage.Type.URL, "https://eu.mc-api.net/v3/server/favicon/" + address + ":" + port + ".png?use-fallback-icon=true");
}
}
@Override
public String minecraftVersion() {
return null;
}
@Override
public int protocolVersion() {
return 0;
}
public String title() {
return name != null ? name : address + (port() != defaultPort() ? ":" + port() : "");
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2021 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/GeyserConnect
*/
package org.geysermc.connect.extension.utils;
public enum ServerCategory {
OFFICIAL("Official"),
GEYSER("Geyser"),
CUSTOM("Custom");
private final String title;
ServerCategory(String title) {
this.title = title;
}
public String title() {
return title;
}
}

View file

@ -0,0 +1,43 @@
package org.geysermc.connect.extension.utils;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.geyser.session.GeyserSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServerManager {
private static final Map<String, List<Server>> servers = new HashMap<>();
public static void loadServers(GeyserSession session) {
GeyserConnect.instance().logger().debug("Loading servers for " + Utils.displayName(session));
servers.put(session.xuid(), GeyserConnect.instance().storageManager().loadServers(session));
}
public static void unloadServers(GeyserSession session) {
GeyserConnect.instance().logger().debug("Saving and unloading servers for " + Utils.displayName(session));
GeyserConnect.instance().storageManager().saveServers(session);
servers.remove(session.xuid());
}
public static List<Server> getServers(GeyserSession session) {
return servers.get(session.xuid());
}
public static void addServer(GeyserSession session, Server server) {
servers.get(session.xuid()).add(server);
}
public static void removeServer(GeyserSession session, Server server) {
getServers(session).remove(server);
}
public static int getServerIndex(GeyserSession session, Server server) {
return getServers(session).indexOf(server);
}
public static void updateServer(GeyserSession session, int serverIndex, Server server) {
getServers(session).set(serverIndex, server);
}
}

View file

@ -0,0 +1,51 @@
package org.geysermc.connect.extension.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.geyser.session.GeyserSession;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
public class Utils {
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public static List<Server> getServers(ServerCategory category) {
return GeyserConnect.instance().config().servers().stream().filter(server -> server.category() == category).toList();
}
public static File fileOrCopiedFromResource(String fileName, String name) throws IOException {
File file = GeyserConnect.instance().dataFolder().resolve(fileName).toFile();
if (!file.exists()) {
try (FileWriter writer = new FileWriter(file)) {
try (FileSystem fileSystem = FileSystems.newFileSystem(new File(GeyserConnect.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath(), Collections.emptyMap())) {
try (InputStream input = Files.newInputStream(fileSystem.getPath(name))) {
byte[] bytes = new byte[input.available()];
input.read(bytes);
writer.write(new String(bytes).toCharArray());
writer.flush();
}
}
} catch (URISyntaxException ignored) { }
}
return file;
}
public static String displayName(GeyserSession session) {
return session.bedrockUsername() + " (" + session.xuid() + ")";
}
}