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

@ -1,110 +0,0 @@
/*
* 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;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.geysermc.connect.storage.AbstractStorageManager;
import org.geysermc.connect.utils.Server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserConnectConfig {
private String address;
private int port;
@JsonProperty("max-players")
private int maxPlayers;
private String motd;
private String submotd = "GeyserConnect";
@JsonProperty("welcome-file")
private String welcomeFile = "welcome.txt";
@JsonProperty("debug-mode")
private boolean debugMode;
private GeyserConfigSection geyser;
private List<Server> servers = new ArrayList<>();
@JsonProperty("custom-servers")
private CustomServersSection customServers;
private VirtualHostSection vhost;
@Getter
public static class GeyserConfigSection {
@JsonProperty("allow-password-authentication")
private boolean allowPasswordAuthentication = false;
@JsonProperty("debug-mode")
private boolean debugMode;
@JsonProperty("saved-user-logins")
private List<String> savedUserLogins = Collections.emptyList();
}
@Getter
public static class CustomServersSection {
private boolean enabled;
private int max;
@JsonProperty("storage-type")
private AbstractStorageManager.StorageType storageType;
private MySQLConnectionSection mysql;
}
@Getter
public static class MySQLConnectionSection {
private String user;
private String pass;
private String database;
private String host;
private int port;
}
@Getter
public static class VirtualHostSection {
private boolean enabled;
@JsonProperty("base-domain")
private String baseDomain;
}
}

View file

@ -1,209 +0,0 @@
/*
* 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;
import com.nukkitx.protocol.bedrock.*;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Getter;
import org.geysermc.connect.proxy.GeyserProxyBootstrap;
import org.geysermc.connect.storage.AbstractStorageManager;
import org.geysermc.connect.storage.DisabledStorageManager;
import org.geysermc.connect.utils.*;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
public class MasterServer {
private BedrockServer bdServer;
@Getter
private boolean shuttingDown = false;
@Getter
private static MasterServer instance;
@Getter
private final Logger logger;
@Getter
private final ScheduledExecutorService generalThreadPool;
@Getter
private final List<Player> players = new ObjectArrayList<>();
@Getter
private GeyserProxyBootstrap geyserProxy;
@Getter
private GeyserConnectConfig geyserConnectConfig;
@Getter
private AbstractStorageManager storageManager;
@Getter
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(new DefaultThreadFactory("Geyser player thread"));
public MasterServer() {
instance = this;
logger = new Logger();
try {
File configFile = GeyserConnectFileUtils.fileOrCopiedFromResource(new File("config.yml"), "config.yml", (x) -> x);
this.geyserConnectConfig = FileUtils.loadConfig(configFile, GeyserConnectConfig.class);
} catch (IOException ex) {
logger.severe("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex);
ex.printStackTrace();
}
logger.setDebug(geyserConnectConfig.isDebugMode());
// As this is only used for fixing the form image bug, we don't need to handle many threads
this.generalThreadPool = Executors.newSingleThreadScheduledExecutor();
// Start a timer to keep the thread running
Timer timer = new Timer();
TimerTask task = new TimerTask() { public void run() { } };
timer.scheduleAtFixedRate(task, 0L, 1000L);
if (!geyserConnectConfig.getCustomServers().isEnabled()) {
// Force the storage manager if we have it disabled
storageManager = new DisabledStorageManager();
logger.info("Disabled custom player servers");
} else {
try {
storageManager = geyserConnectConfig.getCustomServers().getStorageType().getStorageManager().newInstance();
} catch (Exception e) {
logger.severe("Invalid storage manager class!", e);
return;
}
}
storageManager.setupStorage();
// Create the base welcome.txt file
try {
GeyserConnectFileUtils.fileOrCopiedFromResource(new File(getGeyserConnectConfig().getWelcomeFile()), "welcome.txt", (x) -> x);
} catch (IOException ignored) { }
start(geyserConnectConfig.getPort());
logger.start();
}
private void start(int port) {
logger.info("Starting...");
InetSocketAddress bindAddress = new InetSocketAddress(geyserConnectConfig.getAddress(), port);
bdServer = new BedrockServer(bindAddress);
bdServer.setHandler(new BedrockServerEventHandler() {
@Override
public boolean onConnectionRequest(InetSocketAddress address) {
return true; // Connection will be accepted
}
@Override
public BedrockPong onQuery(InetSocketAddress address) {
int playerCount = players.size() + GeyserImpl.getInstance().getSessionManager().size();
String subMotd = geyserConnectConfig.getSubmotd();
if (subMotd == null || subMotd.isEmpty()) {
subMotd = "GeyserConnect";
}
BedrockPong bdPong = new BedrockPong();
bdPong.setEdition("MCPE");
bdPong.setMotd(geyserConnectConfig.getMotd());
bdPong.setSubMotd(subMotd);
bdPong.setPlayerCount(playerCount);
bdPong.setMaximumPlayerCount(geyserConnectConfig.getMaxPlayers());
bdPong.setGameType("Survival");
bdPong.setIpv4Port(port);
bdPong.setProtocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
bdPong.setVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
return bdPong;
}
@Override
public void onSessionCreation(BedrockServerSession session) {
session.setPacketHandler(new PacketHandler(session, instance));
}
});
// Start server up
bdServer.bind().join();
// Create the Geyser instance
createGeyserProxy();
logger.info("Server started on " + geyserConnectConfig.getAddress() + ":" + port);
}
public void shutdown() {
shuttingDown = true;
bdServer.close();
shutdownGeyserProxy();
generalThreadPool.shutdown();
storageManager.closeStorage();
System.exit(0);
}
public void createGeyserProxy() {
if (geyserProxy == null) {
// Make sure Geyser doesn't start the listener
GeyserImpl.setShouldStartListener(false);
this.geyserProxy = new GeyserProxyBootstrap();
geyserProxy.onEnable();
GeyserImpl.getInstance().getPendingMicrosoftAuthentication().setStoreServerInformation();
}
}
public void shutdownGeyserProxy() {
if (geyserProxy != null) {
geyserProxy.onDisable();
geyserProxy = null;
}
}
public List<Server> getServers(ServerCategory serverCategory) {
return getGeyserConnectConfig().getServers().stream().filter(server -> server.getCategory() == serverCategory).collect(Collectors.toList());
}
}

View file

@ -1,427 +0,0 @@
/*
* 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;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nimbusds.jose.shaded.json.JSONArray;
import com.nukkitx.network.util.DisconnectReason;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.ExperimentData;
import com.nukkitx.protocol.bedrock.data.PacketCompressionAlgorithm;
import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.connect.proxy.GeyserProxySession;
import org.geysermc.connect.ui.FormID;
import org.geysermc.connect.ui.UIHandler;
import org.geysermc.connect.utils.GeyserConnectFileUtils;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.response.CustomFormResponse;
import org.geysermc.cumulus.response.FormResponse;
import org.geysermc.cumulus.response.SimpleFormResponse;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication.*;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.geyser.session.auth.BedrockClientData;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.security.interfaces.ECPublicKey;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class PacketHandler implements BedrockPacketHandler {
private final BedrockServerSession session;
private final MasterServer masterServer;
private Player player;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public PacketHandler(BedrockServerSession session, MasterServer masterServer) {
this.session = session;
this.masterServer = masterServer;
session.addDisconnectHandler(this::disconnect);
}
public void disconnect(DisconnectReason reason) {
if (player != null) {
masterServer.getLogger().info(player.getAuthData().name() + " has disconnected from the master server (" + reason + ")");
masterServer.getStorageManager().saveServers(player);
masterServer.getPlayers().remove(player);
}
}
private boolean checkedProtocol = false;
@Override
public boolean handle(RequestNetworkSettingsPacket packet) {
if (checkProtocol(packet.getProtocolVersion())) {
PacketCompressionAlgorithm algorithm = PacketCompressionAlgorithm.ZLIB;
NetworkSettingsPacket responsePacket = new NetworkSettingsPacket();
responsePacket.setCompressionAlgorithm(algorithm);
responsePacket.setCompressionThreshold(512);
session.sendPacketImmediately(responsePacket);
session.setCompression(algorithm);
}
return true;
}
private boolean checkProtocol(int protocolVersion) {
BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
if (packetCodec == null) {
session.setPacketCodec(GameProtocol.DEFAULT_BEDROCK_CODEC);
String message = "disconnectionScreen.internalError.cantConnect";
PlayStatusPacket status = new PlayStatusPacket();
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_SERVER_OLD);
message = "disconnectionScreen.outdatedServer";
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_CLIENT_OLD);
message = "disconnectionScreen.outdatedClient";
}
session.sendPacket(status);
session.disconnect(message);
return false;
}
// Set the session codec
session.setPacketCodec(packetCodec);
return true;
}
@Override
public boolean handle(LoginPacket packet) {
masterServer.getLogger().debug("Login: " + packet.toString());
if (!checkedProtocol) {
if (!checkProtocol(packet.getProtocolVersion())) {
return false;
}
checkedProtocol = true;
}
// Read the raw chain data
JsonNode rawChainData;
try {
rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray());
} catch (IOException e) {
throw new AssertionError("Unable to read chain data!");
}
// Get the parsed chain data
JsonNode chainData = rawChainData.get("chain");
if (chainData.getNodeType() != JsonNodeType.ARRAY) {
throw new AssertionError("Invalid chain data!");
}
try {
// Convert the chainData to a JSONArray
ObjectReader reader = OBJECT_MAPPER.readerFor(new TypeReference<List<String>>() { });
JSONArray array = new JSONArray();
array.addAll(reader.readValue(chainData));
// Verify the chain data
if (!EncryptionUtils.verifyChain(array)) {
// Disconnect the client
session.disconnect("disconnectionScreen.internalError.cantConnect");
throw new AssertionError("Failed to login, due to invalid chain data!");
}
// Parse the signed jws object
JWSObject jwsObject;
jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText());
// Read the JWS payload
JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes());
// Check the identityPublicKey is there
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
throw new AssertionError("Missing identity public key!");
}
// Create an ECPublicKey from the identityPublicKey
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
// Get the skin data to validate the JWS token
JWSObject skinData = JWSObject.parse(packet.getSkinData().toString());
if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) {
// Make sure the client sent over the username, xuid and other info
if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) {
throw new AssertionError("Missing client data");
}
// Fetch the client data
JsonNode extraData = payload.get("extraData");
AuthData authData = new AuthData(
extraData.get("displayName").asText(),
UUID.fromString(extraData.get("identity").asText()),
extraData.get("XUID").asText()
);
// Create a new player and add it to the players list
player = new Player(authData, session);
masterServer.getPlayers().add(player);
player.setChainData(chainData);
// Store the full client data
player.setClientData(OBJECT_MAPPER.convertValue(OBJECT_MAPPER.readTree(skinData.getPayload().toBytes()), BedrockClientData.class));
player.getClientData().setOriginalString(packet.getSkinData().toString());
// Tell the client we have logged in successfully
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
session.sendPacket(playStatusPacket);
// Tell the client there are no resourcepacks
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
session.sendPacket(resourcePacksInfo);
} else {
throw new AssertionError("Invalid identity public key!");
}
} catch (Exception e) {
// Disconnect the client
session.disconnect("disconnectionScreen.internalError.cantConnect");
throw new AssertionError("Failed to login", e);
}
return false;
}
@Override
public boolean handle(ResourcePackClientResponsePacket packet) {
switch (packet.getStatus()) {
case COMPLETED:
masterServer.getLogger().info("Logged in " + player.getAuthData().name() + " (" + player.getAuthData().xuid() + ", " + player.getAuthData().uuid() + ")");
ProxyAuthenticationTask task = (ProxyAuthenticationTask) GeyserImpl.getInstance()
.getPendingMicrosoftAuthentication().getTask(player.getAuthData().xuid());
if (task != null && task.getAuthentication().isDone()) {
String address = task.getServer();
int port = task.getPort();
player.setCurrentServer(new Server(address, port, true, false));
GeyserProxySession session = player.createGeyserSession(false);
session.remoteServer(player.getCurrentServer());
session.onMicrosoftLoginComplete(task);
} else {
player.sendStartGame();
}
break;
case HAVE_ALL_PACKS:
ResourcePackStackPacket stack = new ResourcePackStackPacket();
stack.setExperimentsPreviouslyToggled(false);
stack.setForcedToAccept(false);
stack.setGameVersion("*");
if (!Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion()).getComponentItemData().isEmpty()) {
// Allow custom items to work
stack.getExperiments().add(new ExperimentData("data_driven_items", true));
}
if (session.getPacketCodec().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) {
// Allow extended world height in the overworld to work for pre-1.18 clients
stack.getExperiments().add(new ExperimentData("caves_and_cliffs", true));
}
session.sendPacket(stack);
break;
default:
session.disconnect("disconnectionScreen.resourcePack");
break;
}
return true;
}
@Override
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
masterServer.getLogger().debug("Player initialized: " + player.getAuthData().name());
if (player.getCurrentServer() != null) {
// Player is already logged in via delayed Microsoft authentication
return false;
}
// 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;
}
}
String message = "";
try {
File messageFile = GeyserConnectFileUtils.fileOrCopiedFromResource(new File(MasterServer.getInstance().getGeyserConnectConfig().getWelcomeFile()), "welcome.txt", (x) -> x);
message = new String(FileUtils.readAllBytes(messageFile));
} catch (IOException ignored) { }
if (!message.trim().isEmpty()) {
player.sendWindow(FormID.WELCOME, UIHandler.getMessageWindow(message));
} else {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
}
return false;
}
@Override
public boolean handle(ModalFormResponsePacket packet) {
// Make sure the form is valid
FormID id = FormID.fromId(packet.getFormId());
if (id != player.getCurrentWindowId())
return false;
// Fetch the form and parse the response
Form window = player.getCurrentWindow();
FormResponse response = window.parseResponse(packet.getFormData() == null ? null : packet.getFormData().trim());
// Resend the form if they closed it
if (!response.isCorrect() && !id.isHandlesNull()) {
player.resendWindow();
} else {
// Send the response to the correct response function
switch (id) {
case WELCOME:
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
break;
case MAIN:
UIHandler.handleMainMenuResponse(player, (SimpleFormResponse) response);
break;
case LIST_SERVERS:
UIHandler.handleServerListResponse(player, (SimpleFormResponse) response);
break;
case DIRECT_CONNECT:
UIHandler.handleDirectConnectResponse(player, (CustomFormResponse) response);
break;
case EDIT_SERVERS:
UIHandler.handleEditServerListResponse(player, (SimpleFormResponse) response);
break;
case ADD_SERVER:
UIHandler.handleAddServerResponse(player, (CustomFormResponse) response);
break;
case SERVER_OPTIONS:
UIHandler.handleServerOptionsResponse(player, (SimpleFormResponse) response);
break;
case REMOVE_SERVER:
UIHandler.handleServerRemoveResponse(player, (SimpleFormResponse) response);
break;
case EDIT_SERVER:
UIHandler.handleEditServerResponse(player, (CustomFormResponse) response);
break;
default:
player.resendWindow();
break;
}
}
return true;
}
@Override
public boolean 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
MasterServer.getInstance().getGeneralThreadPool().schedule(() -> session.sendPacket(updateAttributesPacket), 500, TimeUnit.MILLISECONDS);
return false;
}
}

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

@ -23,11 +23,16 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect;
package org.geysermc.connect.extension.config;
public class GeyserConnect {
import com.fasterxml.jackson.annotation.JsonProperty;
import org.geysermc.connect.extension.utils.Server;
public static void main(String[] args) {
new MasterServer();
}
}
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

@ -23,22 +23,26 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
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.*;
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 {
private final ObjectMapper mapper = new ObjectMapper();
protected Connection connection;
@Override
@ -49,8 +53,19 @@ public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
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) {
MasterServer.getInstance().getLogger().severe("Unable to connect to MySQL database!", e);
GeyserConnect.instance().logger().severe("Unable to connect to MySQL database!", e);
}
}
@ -61,37 +76,38 @@ public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
try {
connection.close();
} catch (SQLException exception) {
MasterServer.getInstance().getLogger().error("Failed to close SQL connection", exception);
GeyserConnect.instance().logger().error("Failed to close SQL connection", exception);
}
}
@Override
public void saveServers(Player player) {
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, player.getAuthData().xuid());
updatePlayersServers.setString(2, mapper.writeValueAsString(player.getServers()));
updatePlayersServers.setString(1, session.getAuthData().xuid());
updatePlayersServers.setString(2, Utils.OBJECT_MAPPER.writeValueAsString(ServerManager.getServers(session)));
updatePlayersServers.executeUpdate();
} catch (IOException | SQLException exception) {
MasterServer.getInstance().getLogger().error("Couldn't save servers for " + player.getAuthData().name(), exception);
GeyserConnect.instance().logger().error("Couldn't save servers for " + session.getAuthData().name(), exception);
}
}
@Override
public List<Server> loadServers(Player player) {
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, player.getAuthData().xuid());
getPlayersServers.setString(1, session.getAuthData().xuid());
ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) {
List<Server> loadedServers = mapper.readValue(rs.getString("servers"), new TypeReference<>() {
});
servers.addAll(loadedServers);
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(rs.getString("servers"), new TypeReference<>() {});
if (loadedServers != null) {
servers.addAll(loadedServers);
}
}
} catch (IOException | SQLException exception) {
MasterServer.getInstance().getLogger().error("Couldn't load servers for " + player.getAuthData().name(), exception);
GeyserConnect.instance().logger().error("Couldn't load servers for " + session.getAuthData().name(), exception);
}
return servers;

View file

@ -23,12 +23,12 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
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;
@ -39,26 +39,33 @@ public class AbstractStorageManager {
public void closeStorage() { }
public void saveServers(Player player) { }
public void saveServers(GeyserSession session) { }
public List<Server> loadServers(Player player) {
public List<Server> loadServers(GeyserSession session) {
return new ArrayList<>();
}
@Getter
public enum StorageType {
JSON("json", JsonStorageManager.class),
SQLITE("sqlite", SQLiteStorageManager.class),
MYSQL("mysql", MySQLStorageManager.class);
@JsonValue
private final String name;
private final String configName;
private final Class<? extends AbstractStorageManager> storageManager;
StorageType(String name, Class<? extends AbstractStorageManager> storageManager) {
this.name = name;
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

@ -23,10 +23,10 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.geyser.session.GeyserSession;
import java.util.ArrayList;
import java.util.List;
@ -38,12 +38,12 @@ public class DisabledStorageManager extends AbstractStorageManager {
}
@Override
public void saveServers(Player player) {
public void saveServers(GeyserSession session) {
}
@Override
public List<Server> loadServers(Player player) {
public List<Server> loadServers(GeyserSession session) {
return new ArrayList<>();
}
}

View file

@ -23,12 +23,14 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
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;
@ -37,31 +39,32 @@ import java.util.ArrayList;
import java.util.List;
public class JsonStorageManager extends AbstractStorageManager {
private final ObjectMapper mapper = new ObjectMapper();
private final Path dataFolder = Paths.get("players/");
private Path dataFolder;
@Override
public void setupStorage() {
dataFolder = GeyserConnect.instance().dataFolder().resolve("players/");
if (!dataFolder.toFile().exists()) {
dataFolder.toFile().mkdirs();
}
}
@Override
public void saveServers(Player player) {
public void saveServers(GeyserSession session) {
try {
mapper.writeValue(dataFolder.resolve(player.getAuthData().xuid() + ".json").toFile(), player.getServers());
Utils.OBJECT_MAPPER.writeValue(dataFolder.resolve(session.getAuthData().xuid() + ".json").toFile(), ServerManager.getServers(session));
} catch (IOException ignored) { }
}
@Override
public List<Server> loadServers(Player player) {
public List<Server> loadServers(GeyserSession session) {
List<Server> servers = new ArrayList<>();
try {
List<Server> loadedServers = mapper.readValue(dataFolder.resolve(player.getAuthData().xuid() + ".json").toFile(), new TypeReference<>(){});
servers.addAll(loadedServers);
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

@ -23,18 +23,19 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.GeyserConnectConfig;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.config.MySQLConnectionSection;
import java.sql.*;
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");
GeyserConnectConfig.MySQLConnectionSection connectionInformation = MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().getMysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.getHost() + ":" + connectionInformation.getPort() + "/" + connectionInformation.getDatabase(), connectionInformation.getUser(), connectionInformation.getPass());
MySQLConnectionSection connectionInformation = GeyserConnect.instance().config().customServers().mysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.host() + ":" + connectionInformation.port() + "/" + connectionInformation.database(), connectionInformation.user(), connectionInformation.pass());
}
}

View file

@ -23,14 +23,17 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import java.sql.*;
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:players.db");
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

@ -23,80 +23,32 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
package org.geysermc.connect.extension.utils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
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;
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Server implements RemoteServer {
private String address;
private int port = -1;
private boolean online = true;
private boolean bedrock = false;
private String name = null;
private String imageUrl = null;
private ServerCategory category = null;
public Server(String address) {
this(address, -1);
}
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);
}
public Server(String address, int port, boolean online) {
this(address, port, online, false);
}
public Server(String address, int port, boolean online, boolean bedrock) {
this(address, port, online, bedrock, null);
}
public Server(String address, int port, boolean online, boolean bedrock, String name) {
this(address, port, online, bedrock, name, null);
}
public Server(String address, int port, boolean online, boolean bedrock, String name, String imageUrl) {
this(address.replaceAll(" ", ""), port, online, bedrock, name, imageUrl, ServerCategory.CUSTOM);
this(address, port, true, false, null, null, ServerCategory.CUSTOM);
}
private int defaultPort() { return bedrock ? 19132 : 25565; }
public int getPort() { return port < 0 ? defaultPort() : port; }
@Override
public String toString() {
return name != null ? name : address + (getPort() != defaultPort() ? ":" + getPort() : "");
}
@JsonIgnore
public FormImage getFormImage() {
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 address() {
return address;
}
@Override
public int port() {
return port;
return port < 0 ? defaultPort() : port;
}
@Override
@ -104,6 +56,15 @@ public class Server implements RemoteServer {
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;
@ -113,4 +74,8 @@ public class Server implements RemoteServer {
public int protocolVersion() {
return 0;
}
public String title() {
return name != null ? name : address + (port() != defaultPort() ? ":" + port() : "");
}
}

View file

@ -23,11 +23,8 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
package org.geysermc.connect.extension.utils;
import lombok.Getter;
@Getter
public enum ServerCategory {
OFFICIAL("Official"),
GEYSER("Geyser"),
@ -38,4 +35,8 @@ public enum ServerCategory {
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() + ")";
}
}

View file

@ -1,141 +0,0 @@
/*
* 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.proxy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.geysermc.common.PlatformType;
import org.geysermc.connect.GeyserConnectConfig;
import org.geysermc.connect.MasterServer;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.text.GeyserLocale;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
public class GeyserProxyBootstrap implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private GeyserProxyConfiguration geyserConfig;
private GeyserProxyLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserImpl geyser;
@Override
public void onEnable() {
GeyserLocale.init(this);
// Setup a logger
geyserLogger = new GeyserProxyLogger();
// Read the static config from resources
try {
InputStream configFile = GeyserProxyBootstrap.class.getClassLoader().getResourceAsStream("proxy_config.yml");
// Grab the config as text and replace static strings to the main config variables
String text = new BufferedReader(new InputStreamReader(configFile, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
GeyserConnectConfig multiConfig = MasterServer.getInstance().getGeyserConnectConfig();
text = text.replaceAll("%MOTD%", multiConfig.getMotd());
text = text.replace("%PLAYERS%", String.valueOf(multiConfig.getMaxPlayers()));
text = text.replace("%ALLOWPASSWORDAUTHENTICATION%", String.valueOf(multiConfig.getGeyser().isAllowPasswordAuthentication()));
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
geyserConfig = objectMapper.readValue(text, GeyserProxyConfiguration.class);
geyserConfig.getSavedUserLogins().clear();
for (String savedUserLogin : MasterServer.getInstance().getGeyserConnectConfig().getGeyser().getSavedUserLogins()) {
geyserConfig.getSavedUserLogins().add(savedUserLogin);
}
} catch (IOException ex) {
geyserLogger.severe("Failed to read proxy_config.yml! Make sure it's up to date and/or readable+writable!", ex);
return;
}
// Not sure there is a point in doing this as its a static config
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Create the connector and command manager
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
GeyserImpl.start();
geyserCommandManager = new GeyserCommandManager(geyser);
// Start the ping passthrough thread, again don't think there is a point
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
// Swap the normal handler to our custom handler so we can change some
geyser.getBedrockServer().setHandler(new ProxyConnectorServerEventHandler(geyser));
}
@Override
public void onDisable() {
geyser.shutdown();
}
@Override
public GeyserConfiguration getGeyserConfig() {
return geyserConfig;
}
@Override
public GeyserProxyLogger getGeyserLogger() {
return geyserLogger;
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
}
@Override
public IGeyserPingPassthrough getGeyserPingPassthrough() {
return geyserPingPassthrough;
}
@Override
public Path getConfigFolder() {
return Paths.get(System.getProperty("user.dir"));
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new BootstrapDumpInfo();
}
}

View file

@ -1,42 +0,0 @@
/*
* 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.proxy;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
import java.nio.file.Paths;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserProxyConfiguration extends GeyserJacksonConfiguration {
@Override
public Path getFloodgateKeyPath() {
return Paths.get(getFloodgateKeyFile());
}
}

View file

@ -1,42 +0,0 @@
/*
* 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.proxy;
import lombok.extern.log4j.Log4j2;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Logger;
@Log4j2
public class GeyserProxyLogger extends Logger {
/**
* Disable debug messages depending on config
*/
public void debug(String message) {
if (MasterServer.getInstance().getGeyserConnectConfig().getGeyser().isDebugMode())
super.debug(message);
}
}

View file

@ -1,60 +0,0 @@
/*
* 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.proxy;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import io.netty.channel.EventLoop;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication.*;
public class GeyserProxySession extends GeyserSession {
private final Player player;
public GeyserProxySession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop, Player player, boolean initialized) {
super(geyser, bedrockServerSession, eventLoop);
sentSpawnPacket = initialized;
this.player = player;
}
@Override
protected void disableSrvResolving() {
// Do nothing
}
@Override
public void disconnect(String reason) {
ProxyAuthenticationTask task = (ProxyAuthenticationTask) getGeyser().getPendingMicrosoftAuthentication().getTask(this.xuid());
if (task != null) {
Server server = player.getCurrentServer();
task.setServer(server.getAddress());
task.setPort(server.getPort());
}
super.disconnect(reason);
}
}

View file

@ -1,43 +0,0 @@
/*
* 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.proxy;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.ConnectorServerEventHandler;
public class ProxyConnectorServerEventHandler extends ConnectorServerEventHandler {
public ProxyConnectorServerEventHandler(GeyserImpl geyser) {
super(geyser);
}
@Override
public void onSessionCreation(BedrockServerSession bedrockServerSession) {
super.onSessionCreation(bedrockServerSession);
bedrockServerSession.disconnect("Not sure how you managed it, but you shouldn't be here!");
}
}

View file

@ -1,60 +0,0 @@
/*
* 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.ui;
import lombok.Getter;
@Getter
public enum FormID {
WELCOME,
MAIN,
DIRECT_CONNECT(true),
LIST_SERVERS(true),
EDIT_SERVERS(true),
SERVER_OPTIONS(true),
ADD_SERVER(true),
REMOVE_SERVER,
EDIT_SERVER(true),
CONNECTING,
ERROR;
private final boolean handlesNull;
private static final FormID[] VALUES = values();
FormID() {
this(false);
}
FormID(boolean handlesNull) {
this.handlesNull = handlesNull;
}
public static FormID fromId(int id) {
return id >= 0 && id < VALUES.length ? VALUES[id] : ERROR;
}
}

View file

@ -1,494 +0,0 @@
/*
* 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.ui;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.connect.utils.ServerCategory;
import org.geysermc.cumulus.CustomForm;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.SimpleForm;
import org.geysermc.cumulus.component.InputComponent;
import org.geysermc.cumulus.component.LabelComponent;
import org.geysermc.cumulus.component.ToggleComponent;
import org.geysermc.cumulus.response.CustomFormResponse;
import org.geysermc.cumulus.response.SimpleFormResponse;
import org.geysermc.cumulus.util.FormImage;
import java.util.List;
public class UIHandler {
/**
* Create a list of servers for the client based on the passed servers list
*
* @return A {@link SimpleForm} object
*/
public static Form getMainMenu() {
SimpleForm.Builder window = SimpleForm.builder().title("Main Menu");
window.button("Official Servers");
window.button("Geyser Servers");
// Add a buttons for custom servers
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
window.button("Custom Servers");
window.button("Direct connect");
}
window.button("Disconnect");
return window.build();
}
/**
* Create a list of servers for the client based on the passed servers list
*
* @param servers A list of {@link Server} objects
* @param category The category of the current list
* @return A {@link SimpleForm} object
*/
public static Form getServerList(List<Server> servers, ServerCategory category) {
SimpleForm.Builder window = SimpleForm.builder().title(category.getTitle() + " Servers");
// Add a button for each global server
for (Server server : servers) {
// These images would be better if there was a default to fall back on
// But that would require a web api as bedrock doesn't support doing that
window.button(server.toString(), server.getFormImage());
}
// Add a button for editing
if (category == ServerCategory.CUSTOM) {
window.button("Edit servers");
}
window.button("Back");
return window.build();
}
/**
* Create a direct connect form
*
* @return A {@link CustomForm} object
*/
public static Form getDirectConnect() {
return CustomForm.builder().title("Direct Connect")
.component(InputComponent.of("IP", "play.cubecraft.net"))
.component(InputComponent.of("Port", "25565", "25565"))
.component(ToggleComponent.of("Online mode", true))
.component(ToggleComponent.of("Bedrock/Geyser server", false))
.build();
}
/**
* Create a list of servers for the client to edit
*
* @param servers A list of {@link Server} objects
* @return A {@link SimpleForm} object
*/
public static Form getEditServerList(List<Server> servers) {
SimpleForm.Builder window = SimpleForm.builder().title("Edit Servers").content("Select a server to edit");
// Add a button for each personal server
for (Server server : servers) {
window.button(server.toString(), FormImage.of(FormImage.Type.URL,
"https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png"));
}
window.button("Add server");
window.button("Back");
return window.build();
}
/**
* Create a add server form
*
* @return A {@link CustomForm} object
*/
public static Form getAddServer() {
return CustomForm.builder().title("Add Server")
.component(InputComponent.of("IP", "play.cubecraft.net"))
.component(InputComponent.of("Port", "25565", "25565"))
.component(ToggleComponent.of("Online mode", true))
.component(ToggleComponent.of("Bedrock/Geyser server", false))
.build();
}
/**
* Create a server options form
*
* @param server A {@link Server} object to show options for
* @return A {@link SimpleForm} object
*/
public static Form getServerOptions(Server server) {
SimpleForm.Builder window = SimpleForm.builder().title("Server Options").content(server.toString());
window.button("Edit");
window.button("Remove");
window.button("Back");
return window.build();
}
/**
* Create a remove server form
*
* @param server A {@link Server} object to remove
* @return A {@link SimpleForm} object
*/
public static Form getRemoveServer(Server server) {
return SimpleForm.builder()
.title("Remove Server")
.content("Are you sure you want to remove server: " + server)
.button("Remove")
.button("Cancel")
.build();
}
/**
* Create a edit server form
*
* @param server A {@link Server} object to edit
* @return A {@link CustomForm} object
*/
public static Form getEditServer(int serverIndex, Server server) {
String port = String.valueOf(server.getPort());
return CustomForm.builder()
.component(LabelComponent.of("Server at index: " + serverIndex))
.component(InputComponent.of("IP", server.getAddress(), server.getAddress()))
.component(InputComponent.of("Port", port, port))
.component(ToggleComponent.of("Online mode", server.isOnline()))
.component(ToggleComponent.of("Bedrock/Geyser server", server.isBedrock()))
.build();
}
/**
* Show a basic form window with a message
*
* @param message The message to display
* @return A {@link CustomForm} object
*/
public static Form getMessageWindow(String message) {
return CustomForm.builder()
.title("Notice")
.component(LabelComponent.of(message))
.build();
}
/**
* Handle the main menu response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleMainMenuResponse(Player player, SimpleFormResponse data) {
switch (data.getClickedButtonId()) {
case 0:
player.setServerCategory(ServerCategory.OFFICIAL);
break;
case 1:
player.setServerCategory(ServerCategory.GEYSER);
break;
default:
// If we have custom servers enabled there are a few extra buttons
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
switch (data.getClickedButtonId()) {
case 2:
player.setServerCategory(ServerCategory.CUSTOM);
break;
case 3:
player.sendWindow(FormID.DIRECT_CONNECT, getDirectConnect());
return;
default:
player.getSession().disconnect("disconnectionScreen.disconnected");
return;
}
} else {
player.getSession().disconnect("disconnectionScreen.disconnected");
return;
}
break;
}
// Send the server list
player.sendWindow(FormID.LIST_SERVERS, getServerList(player.getCurrentServers(), player.getServerCategory()));
}
/**
* Handle the server list response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerListResponse(Player player, SimpleFormResponse data) {
List<Server> servers = player.getCurrentServers();
if (player.getServerCategory() == ServerCategory.CUSTOM) {
if (!data.isCorrect() || data.getClickedButtonId() == servers.size() + 1) {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
} else if (data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getCurrentServers()));
} else {
// Get the server
Server server = servers.get(data.getClickedButtonId());
player.sendToServer(server);
}
} else {
if (!data.isCorrect() || data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
} else {
// Get the server
Server server = servers.get(data.getClickedButtonId());
player.sendToServer(server);
}
}
}
/**
* Handle the direct connect response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleDirectConnectResponse(Player player, CustomFormResponse data) {
// Take them back to the main menu if they close the direct connect window
if (!data.isCorrect()) {
player.sendWindow(FormID.MAIN, getMainMenu());
return;
}
try {
String address = data.getInput(0);
int port = Integer.parseInt(data.getInput(1));
boolean online = data.getToggle(2);
boolean bedrock = data.getToggle(3);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.MAIN, getMainMenu());
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.sendToServer(new Server(address, port, online, bedrock));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
/**
* Handle the edit server list response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleEditServerListResponse(Player player, SimpleFormResponse data) {
List<Server> servers = player.getCurrentServers();
// Take them back to the main menu if they close the edit server list window
if (!data.isCorrect()) {
player.sendWindow(FormID.LIST_SERVERS, getServerList(servers, player.getServerCategory()));
return;
}
if (data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.ADD_SERVER, getAddServer());
} else if (data.getClickedButtonId() == servers.size() + 1) {
player.sendWindow(FormID.LIST_SERVERS, getServerList(servers, player.getServerCategory()));
} else {
Server server = player.getServers().get(data.getClickedButtonId());
player.sendWindow(FormID.SERVER_OPTIONS, getServerOptions(server));
}
}
/**
* Handle the add server response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleAddServerResponse(Player player, CustomFormResponse data) {
// Take them back to the edit server list menu if they close the add server window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
try {
String address = data.getInput(0);
int port = Integer.parseInt(data.getInput(1));
boolean online = data.getToggle(2);
boolean bedrock = data.getToggle(3);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.getServers().add(new Server(address, port, online, bedrock));
// Send them back to the edit screen
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
/**
* Handle the server options response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerOptionsResponse(Player player, SimpleFormResponse data) {
// Take them back to the main menu if they close the edit server list window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
SimpleForm window = (SimpleForm) player.getCurrentWindow();
Server selectedServer = null;
for (Server server : player.getServers()) {
if (server.toString().equals(window.getContent())) {
selectedServer = server;
break;
}
}
if (selectedServer == null) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
switch (data.getClickedButtonId()) {
case 0:
player.sendWindow(FormID.EDIT_SERVER, getEditServer(player.getServers().indexOf(selectedServer), selectedServer));
break;
case 1:
player.sendWindow(FormID.REMOVE_SERVER, getRemoveServer(selectedServer));
break;
default:
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
break;
}
}
/**
* Handle the server remove response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerRemoveResponse(Player player, SimpleFormResponse data) {
SimpleForm window = (SimpleForm) player.getCurrentWindow();
String serverName = window.getContent().split(":")[1].trim();
Server selectedServer = null;
for (Server server : player.getServers()) {
if (server.toString().equals(serverName)) {
selectedServer = server;
break;
}
}
if (selectedServer == null) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
if (data.getClickedButtonId() == 0) {
player.getServers().remove(selectedServer);
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} else {
player.sendWindow(FormID.SERVER_OPTIONS, getServerOptions(selectedServer));
}
}
/**
* Handle the edit server response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleEditServerResponse(Player player, CustomFormResponse data) {
// Take them back to the edit server list menu if they close the add server window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
try {
int serverIndex = Integer.parseInt(((CustomForm)player.getCurrentWindow()).getContent().get(0).getText().split(":")[1].trim());
String address = data.getInput(1);
int port = Integer.parseInt(data.getInput(2));
boolean online = data.getToggle(3);
boolean bedrock = data.getToggle(4);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.getServers().set(serverIndex, new Server(address, port, online, bedrock));
// Send them back to the edit screen
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
}

View file

@ -1,63 +0,0 @@
/*
* 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.utils;
import org.geysermc.connect.MasterServer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Function;
public final class GeyserConnectFileUtils {
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> format) throws IOException {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
try (FileOutputStream fos = new FileOutputStream(file)) {
try (InputStream input = MasterServer.class.getClassLoader().getResourceAsStream(name)) {
byte[] bytes = new byte[input.available()];
//noinspection ResultOfMethodCallIgnored
input.read(bytes);
for(char c : format.apply(new String(bytes)).toCharArray()) {
fos.write(c);
}
fos.flush();
}
}
}
return file;
}
private GeyserConnectFileUtils() {
}
}

View file

@ -1,92 +0,0 @@
/*
* 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.utils;
import lombok.extern.log4j.Log4j2;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.connect.MasterServer;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.text.ChatColor;
@Log4j2
public class Logger extends SimpleTerminalConsole implements GeyserLogger {
@Override
protected boolean isRunning() {
return !MasterServer.getInstance().isShuttingDown();
}
@Override
protected void runCommand(String line) {
// Dont do anything rn
}
@Override
protected void shutdown() {
MasterServer.getInstance().shutdown();
}
public void severe(String message) {
log.fatal(printConsole(ChatColor.DARK_RED + message));
}
public void severe(String message, Throwable error) {
log.fatal(printConsole(ChatColor.DARK_RED + message), error);
}
public void error(String message) {
log.error(printConsole(ChatColor.RED + message));
}
public void error(String message, Throwable error) {
log.error(printConsole(ChatColor.RED + message), error);
}
public void warning(String message) {
log.warn(printConsole(ChatColor.YELLOW + message));
}
public void info(String message) {
log.info(printConsole(ChatColor.WHITE + message));
}
public void debug(String message) {
log.debug(printConsole(ChatColor.GRAY + message));
}
public static String printConsole(String message) {
return ChatColor.toANSI(message + ChatColor.RESET);
}
public void setDebug(boolean debug) {
Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel());
}
public boolean isDebug() {
return log.isDebugEnabled();
}
}

View file

@ -1,318 +0,0 @@
/*
* 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.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.nukkitx.math.vector.Vector2f;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.packet.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.proxy.GeyserProxySession;
import org.geysermc.connect.ui.FormID;
import org.geysermc.cumulus.Form;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.UpstreamPacketHandler;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.geyser.session.auth.BedrockClientData;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Getter
public class Player {
private static final byte[] EMPTY_CHUNK_DATA;
static {
ByteBuf byteBuf = Unpooled.buffer();
try {
for (int i = 0; i < 32; i++) {
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
}
byteBuf.writeByte(0); // Border
EMPTY_CHUNK_DATA = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(EMPTY_CHUNK_DATA);
} finally {
byteBuf.release();
}
}
private final AuthData authData;
@Setter
private JsonNode chainData;
private final BedrockServerSession session;
private final List<Server> servers = new ArrayList<>();
private final Long2ObjectMap<ModalFormRequestPacket> forms = new Long2ObjectOpenHashMap<>();
private Form currentWindow;
private FormID currentWindowId;
@Setter
private Server currentServer;
@Setter
private BedrockClientData clientData;
@Setter
private ServerCategory serverCategory;
public Player(AuthData authData, BedrockServerSession session) {
this.authData = authData;
this.session = session;
// Should fetch the servers from some form of db
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
servers.addAll(MasterServer.getInstance().getStorageManager().loadServers(this));
}
}
/**
* Send a few different packets to get the client to load in
*/
public void sendStartGame() {
ItemMappings itemMappings = Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion());
// A lot of this likely doesn't need to be changed
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(1);
startGamePacket.setRuntimeEntityId(1);
startGamePacket.setPlayerGameType(GameType.CREATIVE);
startGamePacket.setPlayerPosition(Vector3f.from(0, 64 + 2, 0));
startGamePacket.setRotation(Vector2f.ONE);
startGamePacket.setSeed(-1L);
startGamePacket.setDimensionId(2);
startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGameType(GameType.CREATIVE);
startGamePacket.setDifficulty(0);
startGamePacket.setDefaultSpawn(Vector3i.ZERO);
startGamePacket.setAchievementsDisabled(true);
startGamePacket.setCurrentTick(-1);
startGamePacket.setEduEditionOffers(0);
startGamePacket.setEduFeaturesEnabled(false);
startGamePacket.setRainLevel(0);
startGamePacket.setLightningLevel(0);
startGamePacket.setMultiplayerGame(true);
startGamePacket.setBroadcastingToLan(true);
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true));
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(true);
startGamePacket.setTexturePacksRequired(false);
startGamePacket.setBonusChestEnabled(false);
startGamePacket.setStartingWithMap(false);
startGamePacket.setTrustingPlayers(true);
startGamePacket.setDefaultPlayerPermission(PlayerPermission.VISITOR);
startGamePacket.setServerChunkTickRange(4);
startGamePacket.setBehaviorPackLocked(false);
startGamePacket.setResourcePackLocked(false);
startGamePacket.setFromLockedWorldTemplate(false);
startGamePacket.setUsingMsaGamertagsOnly(false);
startGamePacket.setFromWorldTemplate(false);
startGamePacket.setWorldTemplateOptionLocked(false);
startGamePacket.setLevelId("");
startGamePacket.setLevelName("GeyserConnect");
startGamePacket.setPremiumWorldTemplateId("");
startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(itemMappings.getItemEntries());
startGamePacket.setInventoriesServerAuthoritative(true);
startGamePacket.setServerEngine("");
startGamePacket.setPlayerPropertyData(NbtMap.EMPTY);
startGamePacket.setWorldTemplateId(UUID.randomUUID());
startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE);
SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings();
settings.setMovementMode(AuthoritativeMovementMode.CLIENT);
settings.setRewindHistorySize(0);
settings.setServerAuthoritativeBlockBreaking(false);
startGamePacket.setPlayerMovementSettings(settings);
startGamePacket.setVanillaVersion("*");
session.sendPacket(startGamePacket);
if (!itemMappings.getComponentItemData().isEmpty()) {
ItemComponentPacket itemComponentPacket = new ItemComponentPacket();
itemComponentPacket.getItems().addAll(itemMappings.getComponentItemData());
session.sendPacket(itemComponentPacket);
}
// Send an empty chunk
LevelChunkPacket data = new LevelChunkPacket();
data.setChunkX(0);
data.setChunkZ(0);
data.setSubChunksLength(0);
data.setData(EMPTY_CHUNK_DATA);
data.setCachingEnabled(false);
session.sendPacket(data);
// Send the biomes
BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket();
biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get());
session.sendPacket(biomeDefinitionListPacket);
AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket();
entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get());
session.sendPacket(entityPacket);
// Send a CreativeContentPacket - required for 1.16.100
CreativeContentPacket creativeContentPacket = new CreativeContentPacket();
creativeContentPacket.setContents(itemMappings.getCreativeItems());
session.sendPacket(creativeContentPacket);
// Let the client know the player can spawn
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendPacket(playStatusPacket);
// Freeze the player
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
setEntityMotionPacket.setRuntimeEntityId(1);
setEntityMotionPacket.setMotion(Vector3f.ZERO);
session.sendPacket(setEntityMotionPacket);
}
/**
* Send a window with the specified id and content
* Also cache it against the player for later use
*
* @param id The {@link FormID} to use for the form
* @param window The {@link Form} to turn into json and send
*/
public void sendWindow(FormID id, Form window) {
this.currentWindow = window;
this.currentWindowId = id;
ModalFormRequestPacket modalFormRequestPacket = new ModalFormRequestPacket();
modalFormRequestPacket.setFormId(id.ordinal());
modalFormRequestPacket.setFormData(window.getJsonData());
session.sendPacket(modalFormRequestPacket);
// This packet is used to fix the image loading bug
NetworkStackLatencyPacket networkStackLatencyPacket = new NetworkStackLatencyPacket();
networkStackLatencyPacket.setFromServer(true);
networkStackLatencyPacket.setTimestamp(System.currentTimeMillis());
session.sendPacket(networkStackLatencyPacket);
}
public void resendWindow() {
sendWindow(currentWindowId, currentWindow);
}
/**
* Send the player to the Geyser proxy server or straight to the bedrock server if it is
*/
public void connectToProxy() {
if (currentServer.isBedrock()) {
TransferPacket transferPacket = new TransferPacket();
transferPacket.setAddress(currentServer.getAddress());
transferPacket.setPort(currentServer.getPort());
session.sendPacket(transferPacket);
} else {
GeyserProxySession geyserSession = createGeyserSession(true);
GeyserImpl geyser = geyserSession.getGeyser();
geyserSession.setDimension(DimensionUtils.THE_END);
geyserSession.remoteServer(currentServer);
// Tell Geyser to handle the login
SetLocalPlayerAsInitializedPacket initializedPacket = new SetLocalPlayerAsInitializedPacket();
initializedPacket.setRuntimeEntityId(geyserSession.getPlayerEntity().getGeyserId());
session.getPacketHandler().handle(initializedPacket);
if (geyser.getConfig().getSavedUserLogins().contains(authData.name())) {
String refreshToken = geyser.refreshTokenFor(authData.name());
if (refreshToken != null) {
geyserSession.authenticateWithRefreshToken(refreshToken);
}
}
if (geyserSession.remoteServer().authType() != AuthType.ONLINE) {
geyserSession.authenticate(geyserSession.getAuthData().name());
}
}
}
public GeyserProxySession createGeyserSession(boolean initialized) {
GeyserProxySession geyserSession = new GeyserProxySession(GeyserImpl.getInstance(), session,
MasterServer.getInstance().getEventLoopGroup().next(), this, initialized);
session.setPacketHandler(new UpstreamPacketHandler(GeyserImpl.getInstance(), geyserSession));
// The player will be tracked from Geyser from here
MasterServer.getInstance().getPlayers().remove(this);
GeyserImpl.getInstance().getSessionManager().addPendingSession(geyserSession);
geyserSession.getUpstream().getSession().setPacketCodec(session.getPacketCodec());
// Set the block translation based off of version
geyserSession.setBlockMappings(BlockRegistries.BLOCKS.forVersion(session.getPacketCodec().getProtocolVersion()));
geyserSession.setItemMappings(Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion()));
geyserSession.setAuthData(authData);
geyserSession.setCertChainData(chainData);
geyserSession.setClientData(clientData);
return geyserSession;
}
public void sendToServer(Server server) {
// Geyser will show a "please wait" message in action bar
// Send the user over to the server
setCurrentServer(server);
connectToProxy();
}
public List<Server> getCurrentServers() {
if (serverCategory == ServerCategory.CUSTOM) {
return servers;
}
return MasterServer.getInstance().getServers(serverCategory);
}
}

View file

@ -2,39 +2,10 @@
# GeyserConnect Configuration File
# --------------------------------
# The IP address that will listen for connections
address: 0.0.0.0
# The port that will listen for connections
port: 19132
# If debug messages should be sent through console
debug-mode: false
# Maximum amount of players that can connect
max-players: 100
# MOTD to display
motd: "GeyserConnect Proxy"
#Sub-MOTD to display. Will be "GeyserConnect" by default if left blank
submotd: "GeyserConnect"
# Welcome message file, if file exists and is not empty this will show on join
# This is loaded live so will update without a server restart
welcome-file: welcome.txt
# Config for the Geyser listener
geyser:
# If password authentication should be allowed in online mode.
allow-password-authentication: false
# If debug messages should be sent through console, has to be enabled in both places to work
debug-mode: false
saved-user-logins:
- ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername
- ThisOtherExampleUsernameShouldAlsoBeLongEnough
# A global list of servers sent to all clients
servers:
- name: The Hive
@ -42,49 +13,49 @@ servers:
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://i.imgur.com/VQJW6XG.png'
imageUrl: 'https://i.imgur.com/myXmr7B.png'
- name: CubeCraft
address: 94.23.159.81
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://www.cubecraft.net/attachments/q3pmrsod_400x400-png.53219/'
imageUrl: 'https://i.imgur.com/f83ZeDS.jpg'
- name: Galaxite
address: 54.39.243.199
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://pbs.twimg.com/profile_images/1275867042583896066/UMPF5nTM_400x400.jpg'
imageUrl: 'https://i.imgur.com/PEkLROT.jpg'
- name: Lifeboat Network
address: 63.143.54.198
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://lbsg.net/wp-content/uploads/2017/06/lifeboat-square.png'
imageUrl: 'https://i.imgur.com/GjsxhCI.png'
- name: Mineplex
address: 108.178.12.38
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://www.mineplex.com/assets/www-mp/img/footer/footer_smalllogo.png'
imageUrl: 'https://i.imgur.com/xB6yncS.png'
- name: MineVille
address: 52.234.130.255
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://pbs.twimg.com/profile_images/1332400307050045441/MHQvGEUP_400x400.jpg'
imageUrl: 'https://i.imgur.com/yqrfMhP.jpg'
- name: Pixel Paradise
address: 40.87.84.59
port: 19132
bedrock: true
category: OFFICIAL
imageUrl: 'https://xforgeassets001.xboxlive.com/pf-title-b63a0803d3653643-20ca2/fa7681c4-673d-40e4-9b6a-61d5d0f93d14/PixelParadise.jpg'
imageUrl: 'https://i.imgur.com/fXQdou8.jpg'
- name: Official Geyser Test Server
address: test.geysermc.org

View file

@ -0,0 +1,6 @@
id: geyserconnect
name: GeyserConnect
main: org.geysermc.connect.extension.GeyserConnect
api: 1.0.0
version: 1.0.0
authors: [rtm516]

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<TerminalConsole name="Console">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %level{length=1} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>

View file

@ -1,196 +0,0 @@
# --------------------------------
# Geyser Configuration File
#
# A bridge between Minecraft: Bedrock Edition and Minecraft: Java Edition.
#
# GitHub: https://github.com/GeyserMC/Geyser
# Discord: https://discord.geysermc.org/
# --------------------------------
bedrock:
# The IP address that will listen for connections.
# There is no reason to change this unless you want to limit what IPs can connect to your server.
address: 0.0.0.0
# The port that will listen for connections
port: 19132
# Some hosting services change your Java port everytime you start the server and require the same port to be used for Bedrock.
# This option makes the Bedrock port the same as the Java port every time you start the server.
# This option is for the plugin version only.
clone-remote-port: false
# The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true
# If either of these are empty, the respective string will default to "Geyser"
motd1: "%MOTD%"
motd2: "%MOTD%"
# The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu.
server-name: "Geyser"
# How much to compress network traffic to the Bedrock client. The higher the number, the more CPU usage used, but
# the smaller the bandwidth used. Does not have any effect below -1 or above 9. Set to -1 to disable.
compression-level: 6
# Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy
# in front of your Geyser instance.
enable-proxy-protocol: false
# A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and
# should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.).
# Keeping this list empty means there is no IP address whitelist.
# Both IP addresses and subnets are supported.
#proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16" ]
remote:
# The IP address of the remote (Java Edition) server
# If it is "auto", for standalone version the remote address will be set to 127.0.0.1,
# for plugin versions, Geyser will attempt to find the best address to connect to.
address: auto
# The port of the remote (Java Edition) server
# For plugin versions, if address has been set to "auto", the port will also follow the server's listening port.
port: 25565
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
auth-type: online
# Allow for password-based authentication methods through Geyser. Only useful in online mode.
# If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
allow-password-authentication: %ALLOWPASSWORDAUTHENTICATION%
# Whether to enable PROXY protocol or not while connecting to the server.
# This is useful only when:
# 1) Your server supports PROXY protocol (it probably doesn't)
# 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
# IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!
use-proxy-protocol: false
# Forward the hostname that the Bedrock client used to connect over to the Java server
# This is designed to be used for forced hosts on proxies
forward-hostname: false
# Floodgate uses encryption to ensure use from authorised sources.
# This should point to the public key generated by Floodgate (Bungee or CraftBukkit)
# You can ignore this when not using Floodgate.
floodgate-key-file: public-key.pem
saved-user-logins:
- ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername
- ThisOtherExampleUsernameShouldAlsoBeLongEnough
# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
command-suggestions: false
# The following three options enable "ping passthrough" - the MOTD, player count and/or protocol name gets retrieved from the Java server.
# Relay the MOTD from the remote server to Bedrock players.
passthrough-motd: false
# Relay the protocol name (e.g. BungeeCord [X.X], Paper 1.X) - only really useful when using a custom protocol name!
# This will also show up on sites like MCSrvStatus. <mcsrvstat.us>
passthrough-protocol-name: false
# Relay the player count and max players from the remote server to Bedrock players.
passthrough-player-counts: false
# Enable LEGACY ping passthrough. There is no need to enable this unless your MOTD or player count does not appear properly.
# This option does nothing on standalone.
legacy-ping-passthrough: false
# How often to ping the remote server, in seconds. Only relevant for standalone or legacy ping passthrough.
# Increase if you are getting BrokenPipe errors.
ping-passthrough-interval: 3
# Whether to forward player ping to the server. While enabling this will allow Bedrock players to have more accurate
# ping, it may also cause players to time out more easily.
forward-player-ping: false
# Maximum amount of players that can connect. This is only visual at this time and does not actually limit player count.
max-players: %PLAYERS%
# If debug messages should be sent through console
debug-mode: false
# Thread pool size
general-thread-pool: 32
# Allow third party capes to be visible. Currently allowing:
# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes
allow-third-party-capes: true
# Allow third party deadmau5 ears to be visible. Currently allowing:
# MinecraftCapes
allow-third-party-ears: false
# Allow a fake cooldown indicator to be sent. Bedrock players do not see a cooldown as they still use 1.8 combat
# Can be title, actionbar or false
show-cooldown: title
# Controls if coordinates are shown to players.
show-coordinates: true
# If set, when a Bedrock player performs any emote, it will swap the offhand and mainhand items, just like the Java Edition keybind
# There are three options this can be set to:
# disabled - the default/fallback, which doesn't apply this workaround
# no-emotes - emotes will NOT be sent to other Bedrock clients and offhand will be swapped. This effectively disables all emotes from being seen.
# emotes-and-offhand - emotes will be sent to Bedrock clients and offhand will be swapped
emote-offhand-workaround: "disabled"
# The default locale if we dont have the one the client requested. Uncomment to not use the default system language.
# default-locale: en_us
# Configures if chunk caching should be enabled or not. This keeps an individual
# record of each block the client loads in. This feature does allow for a few things
# such as more accurate movement that causes less problems with anticheat (meaning
# you're less likely to be banned) and allows block break animations to show up in
# creative mode (and other features). Although this increases RAM usage, it likely
# won't have much of an effect for the vast majority of people. However, if you're
# running out of RAM or are in a RAM-sensitive environment, you may want to disable
# this. When using the Spigot version of Geyser, support for features or
# implementations this allows is automatically enabled without the additional caching
# as Geyser has direct access to the server itself.
cache-chunks: true
# Specify how many days images will be cached to disk to save downloading them from the internet.
# A value of 0 is disabled. (Default: 0)
cache-images: 0
# Allows custom skulls to be displayed. Keeping them enabled may cause a performance decrease on older/weaker devices.
allow-custom-skulls: true
# Whether to add (at this time, only) the furnace minecart as a separate item in the game, which normally does not exist in Bedrock Edition.
# This should only need to be disabled if using a proxy that does not use the "transfer packet" style of server switching.
# If this is disabled, furnace minecart items will be mapped to hopper minecart items.
# This option requires a restart of Geyser in order to change its setting.
add-non-bedrock-items: true
# Bedrock prevents building and displaying blocks above Y127 in the Nether -
# enabling this config option works around that by changing the Nether dimension ID
# to the End ID. The main downside to this is that the sky will resemble that of
# the end sky in the nether, but ultimately it's the only way for this feature to work.
above-bedrock-nether-building: false
# Force clients to load all resource packs if there are any.
# If set to false, it allows the user to connect to the server even if they don't
# want to download the resource packs.
force-resource-packs: true
# Allows Xbox achievements to be unlocked.
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
xbox-achievements-enabled: false
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
# about Geyser, such as how many people are online, how many servers are using Geyser,
# what OS is being used, etc. You can learn more about bStats here: https://bstats.org/.
# https://bstats.org/plugin/server-implementation/GeyserMC
metrics:
# If metrics should be enabled
enabled: false
# UUID of server, don't change!
uuid: generateduuid
# ADVANCED OPTIONS - DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING!
# Geyser updates the Scoreboard after every Scoreboard packet, but when Geyser tries to handle
# a lot of scoreboard packets per second can cause serious lag.
# This option allows you to specify after how many Scoreboard packets per seconds
# the Scoreboard updates will be limited to four updates per second.
scoreboard-packet-threshold: 20
# Allow connections from ProxyPass and Waterdog.
# See https://www.spigotmc.org/wiki/firewall-guide/ for assistance - use UDP instead of TCP.
enable-proxy-connections: false
# The internet supports a maximum MTU of 1492 but could cause issues with packet fragmentation.
# 1400 is the default.
# mtu: 1400
# Whether to use direct server methods to retrieve information such as block states.
# Turning this off for Spigot will stop NMS from being used but will have a performance impact.
use-adapters: true
config-version: 4