mirror of
https://github.com/GeyserMC/GeyserConnect.git
synced 2025-06-26 06:15:21 +02:00
Initial commit
Added base server and a static server list
This commit is contained in:
commit
4a3119f730
14 changed files with 900 additions and 0 deletions
222
.gitignore
vendored
Normal file
222
.gitignore
vendored
Normal file
|
@ -0,0 +1,222 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
|
||||
# Edit at https://www.gitignore.io/?templates=git,java,maven,eclipse,netbeans,jetbrains+all
|
||||
|
||||
### Eclipse ###
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.recommenders
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# PyDev specific (Python IDE for Eclipse)
|
||||
*.pydevproject
|
||||
|
||||
# CDT-specific (C/C++ Development Tooling)
|
||||
.cproject
|
||||
|
||||
# CDT- autotools
|
||||
.autotools
|
||||
|
||||
# Java annotation processor (APT)
|
||||
.factorypath
|
||||
|
||||
# PDT-specific (PHP Development Tools)
|
||||
.buildpath
|
||||
|
||||
# sbteclipse plugin
|
||||
.target
|
||||
|
||||
# Tern plugin
|
||||
.tern-project
|
||||
|
||||
# TeXlipse plugin
|
||||
.texlipse
|
||||
|
||||
# STS (Spring Tool Suite)
|
||||
.springBeans
|
||||
|
||||
# Code Recommenders
|
||||
.recommenders/
|
||||
|
||||
# Annotation Processing
|
||||
.apt_generated/
|
||||
|
||||
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||
.cache-main
|
||||
.scala_dependencies
|
||||
.worksheet
|
||||
|
||||
### Eclipse Patch ###
|
||||
# Eclipse Core
|
||||
.project
|
||||
|
||||
# JDT-specific (Eclipse Java Development Tools)
|
||||
.classpath
|
||||
|
||||
# Annotation Processing
|
||||
.apt_generated
|
||||
|
||||
.sts4-cache/
|
||||
|
||||
### Git ###
|
||||
# Created by git for backups. To disable backups in Git:
|
||||
# $ git config --global mergetool.keepBackup false
|
||||
*.orig
|
||||
|
||||
# Created by git when using merge tools for conflicts
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
|
||||
### Java ###
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
### JetBrains+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### JetBrains+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### Maven ###
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### NetBeans ###
|
||||
**/nbproject/private/
|
||||
**/nbproject/Makefile-*.mk
|
||||
**/nbproject/Package-*.bash
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
|
||||
# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
|
79
pom.xml
Normal file
79
pom.xml
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>geyser-multi</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>GeyserMulti</name>
|
||||
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers via a masterServer list.</description>
|
||||
<url>https://geysermc.org</url>
|
||||
|
||||
<properties>
|
||||
<outputName>GeyserMulti</outputName>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>nukkitx-release-repo</id>
|
||||
<url>https://repo.nukkitx.com/maven-releases/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>nukkitx-snapshot-repo</id>
|
||||
<url>https://repo.nukkitx.com/maven-snapshots/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc</groupId>
|
||||
<artifactId>connector</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.protocol</groupId>
|
||||
<artifactId>bedrock-v390</artifactId>
|
||||
<version>2.5.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.minecrell</groupId>
|
||||
<artifactId>terminalconsoleappender</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>org.geysermc.multi.GeyserMulti</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
16
src/main/java/org/geysermc/multi/GeyserMulti.java
Normal file
16
src/main/java/org/geysermc/multi/GeyserMulti.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
|
||||
import com.nukkitx.protocol.bedrock.v390.Bedrock_v390;
|
||||
|
||||
public class GeyserMulti {
|
||||
|
||||
public static final BedrockPacketCodec CODEC = Bedrock_v390.V390_CODEC;
|
||||
|
||||
private static MasterServer masterServer;
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
new MasterServer();
|
||||
}
|
||||
}
|
75
src/main/java/org/geysermc/multi/Logger.java
Normal file
75
src/main/java/org/geysermc/multi/Logger.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.geysermc.common.ChatColor;
|
||||
|
||||
@Log4j2
|
||||
public class Logger extends SimpleTerminalConsole {
|
||||
|
||||
private boolean colored = true;
|
||||
|
||||
@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, colored));
|
||||
}
|
||||
|
||||
public void severe(String message, Throwable error) {
|
||||
log.fatal(printConsole(ChatColor.DARK_RED + message, colored), error);
|
||||
}
|
||||
|
||||
public void error(String message) {
|
||||
log.error(printConsole(ChatColor.RED + message, colored));
|
||||
}
|
||||
|
||||
public void error(String message, Throwable error) {
|
||||
log.error(printConsole(ChatColor.RED + message, colored), error);
|
||||
}
|
||||
|
||||
public void warning(String message) {
|
||||
log.warn(printConsole(ChatColor.YELLOW + message, colored));
|
||||
}
|
||||
|
||||
public void info(String message) {
|
||||
log.info(printConsole(ChatColor.WHITE + message, colored));
|
||||
}
|
||||
|
||||
public void debug(String message) {
|
||||
log.debug(printConsole(ChatColor.GRAY + message, colored));
|
||||
}
|
||||
|
||||
public static String printConsole(String message, boolean colors) {
|
||||
return colors ? ChatColor.toANSI(message + ChatColor.RESET) : ChatColor.stripColors(message + ChatColor.RESET);
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "CONSOLE";
|
||||
}
|
||||
|
||||
public void sendMessage(String message) {
|
||||
info(message);
|
||||
}
|
||||
|
||||
public boolean isConsole() {
|
||||
return true;
|
||||
}
|
||||
}
|
97
src/main/java/org/geysermc/multi/MasterServer.java
Normal file
97
src/main/java/org/geysermc/multi/MasterServer.java
Normal file
|
@ -0,0 +1,97 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.BedrockPong;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServer;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServerSession;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class MasterServer {
|
||||
|
||||
private final Timer timer;
|
||||
private BedrockServer bdServer;
|
||||
private BedrockPong bdPong;
|
||||
|
||||
@Getter
|
||||
private boolean shuttingDown = false;
|
||||
|
||||
@Getter
|
||||
private static MasterServer instance;
|
||||
|
||||
@Getter
|
||||
private final Logger logger;
|
||||
|
||||
@Getter
|
||||
private final ScheduledExecutorService generalThreadPool;
|
||||
|
||||
@Getter
|
||||
private final Map<InetSocketAddress, Player> players = new HashMap<>();
|
||||
|
||||
public MasterServer() {
|
||||
logger = new Logger();
|
||||
logger.setDebug(true);
|
||||
|
||||
this.instance = this;
|
||||
this.generalThreadPool = Executors.newScheduledThreadPool(32);
|
||||
|
||||
// Start a timer to keep the thread running
|
||||
timer = new Timer();
|
||||
TimerTask task = new TimerTask() { public void run() { } };
|
||||
timer.scheduleAtFixedRate(task, 0L, 1000L);
|
||||
|
||||
start(19132);
|
||||
|
||||
logger.start();
|
||||
}
|
||||
|
||||
private void start(int port) {
|
||||
logger.info("Starting...");
|
||||
|
||||
InetSocketAddress bindAddress = new InetSocketAddress("0.0.0.0", port);
|
||||
bdServer = new BedrockServer(bindAddress);
|
||||
|
||||
bdPong = new BedrockPong();
|
||||
bdPong.setEdition("MCPE");
|
||||
bdPong.setMotd("My Server");
|
||||
bdPong.setPlayerCount(0);
|
||||
bdPong.setMaximumPlayerCount(1337);
|
||||
bdPong.setGameType("Survival");
|
||||
bdPong.setIpv4Port(port);
|
||||
bdPong.setProtocolVersion(GeyserMulti.CODEC.getProtocolVersion());
|
||||
bdPong.setVersion(GeyserMulti.CODEC.getMinecraftVersion());
|
||||
|
||||
bdServer.setHandler(new BedrockServerEventHandler() {
|
||||
@Override
|
||||
public boolean onConnectionRequest(InetSocketAddress address) {
|
||||
return true; // Connection will be accepted
|
||||
}
|
||||
|
||||
@Override
|
||||
public BedrockPong onQuery(InetSocketAddress address) {
|
||||
return bdPong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionCreation(BedrockServerSession session) {
|
||||
session.setPacketHandler(new PacketHandler(session, instance));
|
||||
}
|
||||
});
|
||||
|
||||
// Start server up
|
||||
bdServer.bind().join();
|
||||
logger.info("Server started on 0.0.0.0:" + port);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shuttingDown = true;
|
||||
generalThreadPool.shutdown();
|
||||
}
|
||||
}
|
154
src/main/java/org/geysermc/multi/PacketHandler.java
Normal file
154
src/main/java/org/geysermc/multi/PacketHandler.java
Normal file
|
@ -0,0 +1,154 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
|
||||
import com.nukkitx.network.util.DisconnectReason;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServerSession;
|
||||
import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
||||
import org.geysermc.multi.UI.UIHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PacketHandler implements BedrockPacketHandler {
|
||||
|
||||
private BedrockServerSession session;
|
||||
private 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((reason) -> disconnect(reason));
|
||||
}
|
||||
|
||||
public void disconnect(DisconnectReason reason) {
|
||||
if (player != null) {
|
||||
masterServer.getLogger().info(player.getDisplayName() + " has disconnected (" + reason + ")");
|
||||
masterServer.getPlayers().remove(session.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPacket packet) {
|
||||
masterServer.getLogger().debug("Login: " + packet.toString());
|
||||
|
||||
// Check the protocol version is correct
|
||||
int protocol = packet.getProtocolVersion();
|
||||
if (protocol != GeyserMulti.CODEC.getProtocolVersion()) {
|
||||
PlayStatusPacket status = new PlayStatusPacket();
|
||||
if (protocol > GeyserMulti.CODEC.getProtocolVersion()) {
|
||||
status.setStatus(PlayStatusPacket.Status.FAILED_SERVER);
|
||||
} else {
|
||||
status.setStatus(PlayStatusPacket.Status.FAILED_CLIENT);
|
||||
}
|
||||
session.sendPacket(status);
|
||||
}
|
||||
|
||||
// Set the session codec
|
||||
session.setPacketCodec(GeyserMulti.CODEC);
|
||||
|
||||
JsonNode rawChainData;
|
||||
try {
|
||||
rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Unable to read chain data!");
|
||||
}
|
||||
|
||||
JsonNode chainData = rawChainData.get("chain");
|
||||
if (chainData.getNodeType() != JsonNodeType.ARRAY) {
|
||||
throw new AssertionError("Invalid chain data!");
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the signed jws object
|
||||
JWSObject jwsObject;
|
||||
jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText());
|
||||
|
||||
JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes());
|
||||
|
||||
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
|
||||
throw new AssertionError("Missing identity public key!");
|
||||
}
|
||||
|
||||
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
|
||||
|
||||
JWSObject skinData = JWSObject.parse(packet.getSkinData().toString());
|
||||
if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) {
|
||||
if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) {
|
||||
throw new AssertionError("Missing client data");
|
||||
}
|
||||
|
||||
JsonNode extraData = payload.get("extraData");
|
||||
|
||||
player = new Player(extraData, session);
|
||||
masterServer.getPlayers().put(session.getAddress(), player);
|
||||
|
||||
// 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.getDisplayName() + " (" + player.getXuid() + ")");
|
||||
player.sendStartGame();
|
||||
break;
|
||||
case HAVE_ALL_PACKS:
|
||||
ResourcePackStackPacket stack = new ResourcePackStackPacket();
|
||||
stack.setExperimental(false);
|
||||
stack.setForcedToAccept(false);
|
||||
stack.setGameVersion("*");
|
||||
session.sendPacket(stack);
|
||||
break;
|
||||
default:
|
||||
session.disconnect("disconnectionScreen.resourcePack");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
|
||||
masterServer.getLogger().debug("Player initialized: " + player.getDisplayName());
|
||||
|
||||
player.sendServerList();
|
||||
|
||||
/*TransferPacket transferPacket = new TransferPacket();
|
||||
transferPacket.setAddress("81.174.164.211");
|
||||
transferPacket.setPort(27040);
|
||||
session.sendPacket(transferPacket);*/
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
62
src/main/java/org/geysermc/multi/PalleteManger.java
Normal file
62
src/main/java/org/geysermc/multi/PalleteManger.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import com.nukkitx.nbt.CompoundTagBuilder;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.nbt.stream.NBTInputStream;
|
||||
import com.nukkitx.nbt.stream.NBTOutputStream;
|
||||
import com.nukkitx.nbt.tag.CompoundTag;
|
||||
import com.nukkitx.nbt.tag.ListTag;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.utils.FileUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This class is mostly copied from core Geyser
|
||||
*/
|
||||
public class PalleteManger {
|
||||
public static final ListTag<CompoundTag> BLOCK_PALLETE;
|
||||
public static final CompoundTag BIOMES_PALLETE;
|
||||
public static final byte[] EMPTY_LEVEL_CHUNK_DATA;
|
||||
|
||||
private static final com.nukkitx.nbt.tag.CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag();
|
||||
|
||||
static {
|
||||
/* Load block palette */
|
||||
InputStream stream = FileUtils.getResource("runtime_block_states.dat");
|
||||
|
||||
try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)) {
|
||||
BLOCK_PALLETE = (ListTag<CompoundTag>) nbtInputStream.readTag();
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to get blocks from runtime block states", e);
|
||||
}
|
||||
|
||||
/* Load biomes */
|
||||
stream = FileUtils.getResource("biome_definitions.dat");
|
||||
|
||||
try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream)){
|
||||
BIOMES_PALLETE = (CompoundTag) nbtInputStream.readTag();
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Failed to get biomes from biome definitions", e);
|
||||
}
|
||||
|
||||
/* Create empty chunk data */
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size
|
||||
|
||||
try (NBTOutputStream nbtOutputStream = NbtUtils.createNetworkWriter(outputStream)) {
|
||||
nbtOutputStream.write(EMPTY_TAG);
|
||||
}
|
||||
|
||||
EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray();
|
||||
}catch (IOException e) {
|
||||
throw new AssertionError("Unable to generate empty level chunk data");
|
||||
}
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// no-op
|
||||
}
|
||||
}
|
120
src/main/java/org/geysermc/multi/Player.java
Normal file
120
src/main/java/org/geysermc/multi/Player.java
Normal file
|
@ -0,0 +1,120 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
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.protocol.bedrock.BedrockServerSession;
|
||||
import com.nukkitx.protocol.bedrock.data.*;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.multi.UI.UIHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class Player {
|
||||
|
||||
private String xuid;
|
||||
private UUID identity;
|
||||
private String displayName;
|
||||
|
||||
private BedrockServerSession session;
|
||||
|
||||
private final List<Server> servers = new ArrayList<>();
|
||||
|
||||
public Player(JsonNode extraData, BedrockServerSession session) {
|
||||
this.xuid = extraData.get("XUID").asText();
|
||||
this.identity = UUID.fromString(extraData.get("identity").asText());
|
||||
this.displayName = extraData.get("displayName").asText();
|
||||
|
||||
this.session = session;
|
||||
|
||||
// Should fetch the servers from some form of db
|
||||
servers.add(new Server("mc.hypixel.net"));
|
||||
servers.add(new Server("81.174.164.211", 25580));
|
||||
}
|
||||
|
||||
public void sendStartGame() {
|
||||
StartGamePacket startGamePacket = new StartGamePacket();
|
||||
startGamePacket.setUniqueEntityId(1);
|
||||
startGamePacket.setRuntimeEntityId(1);
|
||||
startGamePacket.setPlayerGamemode(0);
|
||||
startGamePacket.setPlayerPosition(Vector3f.from(0, 64 + 2, 0));
|
||||
startGamePacket.setRotation(Vector2f.ONE);
|
||||
|
||||
startGamePacket.setSeed(-1);
|
||||
startGamePacket.setDimensionId(2);
|
||||
startGamePacket.setGeneratorId(1);
|
||||
startGamePacket.setLevelGamemode(1);
|
||||
startGamePacket.setDifficulty(0);
|
||||
startGamePacket.setDefaultSpawn(Vector3i.ZERO);
|
||||
startGamePacket.setAchievementsDisabled(true);
|
||||
startGamePacket.setTime(-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.setWorldName("GeyserMulti");
|
||||
startGamePacket.setPremiumWorldTemplateId("");
|
||||
startGamePacket.setCurrentTick(0);
|
||||
startGamePacket.setEnchantmentSeed(0);
|
||||
startGamePacket.setMultiplayerCorrelationId("");
|
||||
startGamePacket.setBlockPalette(PalleteManger.BLOCK_PALLETE);
|
||||
//startGamePacket.setItemEntries(ItemRegistry.ITEMS);
|
||||
startGamePacket.setVanillaVersion("*");
|
||||
// startGamePacket.setMovementServerAuthoritative(true);
|
||||
session.sendPacket(startGamePacket);
|
||||
|
||||
// Send an empty chunk
|
||||
LevelChunkPacket data = new LevelChunkPacket();
|
||||
data.setChunkX(0);
|
||||
data.setChunkZ(0);
|
||||
data.setSubChunksLength(0);
|
||||
data.setData(PalleteManger.EMPTY_LEVEL_CHUNK_DATA);
|
||||
data.setCachingEnabled(false);
|
||||
session.sendPacket(data);
|
||||
|
||||
// Send the biomes
|
||||
BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket();
|
||||
biomeDefinitionListPacket.setTag(PalleteManger.BIOMES_PALLETE);
|
||||
session.sendPacket(biomeDefinitionListPacket);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void sendServerList() {
|
||||
session.sendPacket(UIHandler.getServerListFormPacket(servers));
|
||||
}
|
||||
}
|
16
src/main/java/org/geysermc/multi/Server.java
Normal file
16
src/main/java/org/geysermc/multi/Server.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package org.geysermc.multi;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class Server {
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public Server(String address) {
|
||||
this(address, 25565);
|
||||
}
|
||||
}
|
9
src/main/java/org/geysermc/multi/UI/FormID.java
Normal file
9
src/main/java/org/geysermc/multi/UI/FormID.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package org.geysermc.multi.UI;
|
||||
|
||||
public enum FormID {
|
||||
MAIN,
|
||||
DIRECT_CONNECT,
|
||||
ADD_SERVER,
|
||||
REMOVE_SERVER,
|
||||
ERROR;
|
||||
}
|
29
src/main/java/org/geysermc/multi/UI/UIHandler.java
Normal file
29
src/main/java/org/geysermc/multi/UI/UIHandler.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
package org.geysermc.multi.UI;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket;
|
||||
import org.geysermc.common.window.FormWindow;
|
||||
import org.geysermc.common.window.SimpleFormWindow;
|
||||
import org.geysermc.common.window.button.FormButton;
|
||||
import org.geysermc.common.window.button.FormImage;
|
||||
import org.geysermc.multi.Server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UIHandler {
|
||||
public static ModalFormRequestPacket getServerListFormPacket(List<Server> servers) {
|
||||
SimpleFormWindow window = new SimpleFormWindow("Servers", "");
|
||||
|
||||
for (Server server : servers) {
|
||||
window.getButtons().add(new FormButton(server.getAddress(), new FormImage(FormImage.FormImageType.URL, "https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png")));
|
||||
}
|
||||
|
||||
return generatePacket(FormID.MAIN, window);
|
||||
}
|
||||
|
||||
private static ModalFormRequestPacket generatePacket(FormID id, FormWindow form) {
|
||||
ModalFormRequestPacket modalFormRequestPacket = new ModalFormRequestPacket();
|
||||
modalFormRequestPacket.setFormId(id.ordinal());
|
||||
modalFormRequestPacket.setFormData(form.getJSONData()); // This fixes a bug in Geyser
|
||||
return modalFormRequestPacket;
|
||||
}
|
||||
}
|
BIN
src/main/resources/biome_definitions.dat
Normal file
BIN
src/main/resources/biome_definitions.dat
Normal file
Binary file not shown.
21
src/main/resources/log4j2.xml
Normal file
21
src/main/resources/log4j2.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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>
|
BIN
src/main/resources/runtime_block_states.dat
Normal file
BIN
src/main/resources/runtime_block_states.dat
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue