forked from git-mirrors/GeyserConnect
Rewrite to move to a Geyser extension
This commit is contained in:
parent
6b6973c5b9
commit
91bc9657a4
49 changed files with 1350 additions and 2796 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
142
src/main/java/org/geysermc/connect/extension/PacketHandler.java
Normal file
142
src/main/java/org/geysermc/connect/extension/PacketHandler.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.geysermc.connect.extension.config;
|
||||
|
||||
public record MySQLConnectionSection(
|
||||
String user,
|
||||
String pass,
|
||||
String database,
|
||||
String host,
|
||||
int port) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
314
src/main/java/org/geysermc/connect/extension/ui/UIHandler.java
Normal file
314
src/main/java/org/geysermc/connect/extension/ui/UIHandler.java
Normal 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);
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -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() : "");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() + ")";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
6
src/main/resources/extension.yml
Normal file
6
src/main/resources/extension.yml
Normal 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]
|
|
@ -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>
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue