Rewrite to move to a Geyser extension

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

36
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Build Pull Request
on:
push:
paths-ignore:
- '.idea/copyright/*.xml'
- '.gitignore'
- 'LICENSE'
- 'README.md'
- 'bind9/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
distribution: temurin
- name: Checkout repository
uses: actions/checkout@v1
- name: Build Geyser
uses: gradle/gradle-build-action@v2
with:
arguments: build
- name: Archive artifacts
uses: actions/upload-artifact@v1
if: success()
with:
name: Build
path: build/libs/GeyserConnect.jar

View file

@ -1,29 +1,36 @@
name: Build Pull Request
on: [pull_request]
on:
pull_request:
paths-ignore:
- '.idea/copyright/*.xml'
- '.gitignore'
- 'LICENSE'
- 'README.md'
- 'bind9/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/cache@v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 16
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: 16
- name: Build with Maven
run: mvn -B package
java-version: 17
distribution: temurin
- name: Checkout repository
uses: actions/checkout@v1
- name: Build Geyser
uses: gradle/gradle-build-action@v2
with:
arguments: build
- name: Archive artifacts
uses: actions/upload-artifact@v1
if: success()
with:
name: Build
path: target/GeyserConnect.jar
path: build/libs/GeyserConnect.jar

96
.gitignore vendored
View file

@ -1,6 +1,6 @@
# 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
# Created by https://www.toptal.com/developers/gitignore/api/git,java,gradle,eclipse,netbeans,jetbrains+all
# Edit at https://www.toptal.com/developers/gitignore?templates=git,java,gradle,eclipse,netbeans,jetbrains+all
### Eclipse ###
.metadata
@ -53,22 +53,19 @@ local.properties
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
# Spring Boot Tooling
.sts4-cache/
### Git ###
@ -110,9 +107,10 @@ local.properties
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
@ -122,6 +120,9 @@ hs_err_pid*
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
@ -142,6 +143,9 @@ hs_err_pid*
# 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/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
@ -169,6 +173,9 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
@ -182,32 +189,13 @@ fabric.properties
.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
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
#.idea/
.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
!.idea/codeStyles
!.idea/runConfigurations
### NetBeans ###
**/nbproject/private/
@ -219,15 +207,35 @@ dist/
nbdist/
.nb-gradle/
# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
### Gradle ###
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Gradle Patch ###
# Java heap dump
*.hprof
# End of https://www.toptal.com/developers/gitignore/api/git,java,gradle,eclipse,netbeans,jetbrains+all
# Copyright header files
.idea/*
!.idea/copyright/
/config.yml
/welcome.txt
/logs/
/locales/
/players/
players.db

77
Jenkinsfile vendored
View file

@ -1,77 +0,0 @@
pipeline {
agent any
tools {
maven 'Maven 3'
jdk 'Java 16'
}
parameters{
booleanParam(defaultValue: false, description: 'Skip Discord notification', name: 'SKIP_DISCORD')
}
options {
buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
}
stages {
stage ('Build') {
steps {
sh 'git submodule update --init --recursive'
rtMavenRun(
pom: 'pom.xml',
goals: 'clean package'
)
}
post {
success {
archiveArtifacts artifacts: 'target/*.jar', excludes: 'target/geyser-connect-*.jar', fingerprint: true
}
}
}
}
post {
always {
script {
def changeLogSets = currentBuild.changeSets
def message = "**Changes:**"
if (changeLogSets.size() == 0) {
message += "\n*No changes.*"
} else {
def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "")
def count = 0;
def extra = 0;
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
if (count <= 10) {
def entry = entries[j]
def commitId = entry.commitId.substring(0, 6)
message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}"
count++
} else {
extra++;
}
}
}
if (extra != 0) {
message += "\n - ${extra} more commits"
}
}
env.changes = message
}
deleteDir()
script {
if(!params.SKIP_DISCORD) {
withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) {
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/GeyserMC/job/GeyserConnect)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
}
}
}
}

View file

@ -3,17 +3,13 @@
[![forthebadge made-with-java](https://forthebadge.com/images/badges/made-with-java.svg)](https://java.com/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Build Status](https://ci.nukkitx.com/job/GeyserMC/job/GeyserConnect/job/master/badge/icon)](https://ci.nukkitx.com/job/GeyserMC/job/GeyserConnect/job/master/)
[![Build Status](https://github.com/GeyserMC/GeyserConnect/actions/workflows/build.yml/badge.svg)](https://github.com/GeyserMC/GeyserConnect/actions/workflows/build.yml)
[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/)
[![HitCount](http://hits.dwyl.com/GeyserMC/GeyserConnect.svg)](http://hits.dwyl.io/GeyserMC/GeyserConnect)
GeyserConnect is an easy way for Bedrock Edition clients to connect to any Java Edition servers without having to run anything.
## What is GeyserConnect?
GeyserConnect is a server that Minecraft: Bedrock Edition clients can connect to that allows for a list of Minecraft: Java Edition servers to be displayed and accessed through 1 public Geyser instance. It is effectively a combination of [BedrockConnect](https://github.com/Pugmatt/BedrockConnect) and [Geyser](https://github.com/GeyserMC/Geyser).
GeyserConnect is an extension for Geyser that allows for a list of Minecraft: Java Edition servers to be displayed and accessed through 1 public Geyser instance. It is effectively give the customisability of [BedrockConnect](https://github.com/Pugmatt/BedrockConnect) to [Geyser](https://github.com/GeyserMC/Geyser).
**Please note, this project is still a work in progress and should not be used on production. Expect bugs!**
If you wish to run this in docker and/or use DNS redirection please see the appropriate folders in this repo.
#### Docker: [here](docker)
#### DNS: [here](bind9)
If you wish to use DNS redirection please see the [bind9](bind9) folder in this repository.

59
build.gradle Normal file
View file

@ -0,0 +1,59 @@
plugins {
id 'java-library'
id 'java'
}
group 'org.geysermc.connect.extension'
version '1.0.0'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'https://repo.opencollab.dev/main'
}
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots'
mavenContent {
snapshotsOnly()
}
}
maven {
url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
}
maven {
url 'https://jitpack.io'
}
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
compileOnly 'org.geysermc.geyser:api:2.1.0-SNAPSHOT'
compileOnly('org.geysermc.geyser:core:2.1.0-SNAPSHOT') {
exclude group: 'io.netty'
}
implementation 'org.xerial:sqlite-jdbc:3.41.2.1'
implementation 'com.mysql:mysql-connector-j:8.0.33'
}
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveFileName = "${project.name}.jar"
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
test {
useJUnitPlatform()
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View file

@ -1,5 +0,0 @@
FROM openjdk:8-jre-slim
RUN mkdir /gsc
WORKDIR /gsc
EXPOSE 19132/udp
CMD ["java", "-Xms1G", "-jar", "GeyserConnect.jar"]

View file

@ -1,10 +0,0 @@
# GeyserConnect using Docker
This contains the docker image and a basic way of running GeyserConnect
## Setup
1. Download GeyserConnect to a subfolder called `data`
2. Build the docker file using `docker build -t geyser-connect .`
3. Start geyser using this:
```
docker run --name "geyser-c" -d --restart always -p 19132:19132/udp -v $(pwd)/data:/gsc geyser-connect
```

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

240
gradlew vendored Normal file
View file

@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

91
gradlew.bat vendored Normal file
View file

@ -0,0 +1,91 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

160
pom.xml
View file

@ -1,160 +0,0 @@
<?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-connect</artifactId>
<version>1.0-SNAPSHOT</version>
<name>GeyserConnect</name>
<description>Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers via a server list.</description>
<url>https://geysermc.org</url>
<properties>
<outputName>GeyserConnect</outputName>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<organization>
<name>GeyserMC</name>
<url>https://github.com/GeyserMC/GeyserConnect/blob/master/pom.xml</url>
</organization>
<scm>
<connection>scm:git:https://github.com/GeyserMC/GeyserConnect.git</connection>
<developerConnection>scm:git:git@github.com:GeyserMC/GeyserConnect.git</developerConnection>
<url>https://github.com/GeyserMC/GeyserConnect</url>
</scm>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>opencollab-release-repo</id>
<url>https://repo.opencollab.dev/maven-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>opencollab-snapshot-repo</id>
<url>https://repo.opencollab.dev/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>sonatype-s01</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>core</artifactId>
<version>2.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.minecrell</groupId>
<artifactId>terminalconsoleappender</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.31.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.geysermc.connect.GeyserConnect</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<dependencies>
<dependency>
<groupId>com.github.edwgiz</groupId>
<artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
<version>2.8.1</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${outputName}</finalName>
</configuration>
</execution>
</executions>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/versions/9/module-info.class</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.geysermc.connect.GeyserConnect</mainClass>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</transformer>
<transformer
implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer">
</transformer>
</transformers>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
</configuration>
</plugin>
</plugins>
</build>
</project>

2
settings.gradle Normal file
View file

@ -0,0 +1,2 @@
rootProject.name = 'GeyserConnect'

View file

@ -1,110 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.geysermc.connect.storage.AbstractStorageManager;
import org.geysermc.connect.utils.Server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserConnectConfig {
private String address;
private int port;
@JsonProperty("max-players")
private int maxPlayers;
private String motd;
private String submotd = "GeyserConnect";
@JsonProperty("welcome-file")
private String welcomeFile = "welcome.txt";
@JsonProperty("debug-mode")
private boolean debugMode;
private GeyserConfigSection geyser;
private List<Server> servers = new ArrayList<>();
@JsonProperty("custom-servers")
private CustomServersSection customServers;
private VirtualHostSection vhost;
@Getter
public static class GeyserConfigSection {
@JsonProperty("allow-password-authentication")
private boolean allowPasswordAuthentication = false;
@JsonProperty("debug-mode")
private boolean debugMode;
@JsonProperty("saved-user-logins")
private List<String> savedUserLogins = Collections.emptyList();
}
@Getter
public static class CustomServersSection {
private boolean enabled;
private int max;
@JsonProperty("storage-type")
private AbstractStorageManager.StorageType storageType;
private MySQLConnectionSection mysql;
}
@Getter
public static class MySQLConnectionSection {
private String user;
private String pass;
private String database;
private String host;
private int port;
}
@Getter
public static class VirtualHostSection {
private boolean enabled;
@JsonProperty("base-domain")
private String baseDomain;
}
}

View file

@ -1,209 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect;
import com.nukkitx.protocol.bedrock.*;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.util.concurrent.DefaultThreadFactory;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import lombok.Getter;
import org.geysermc.connect.proxy.GeyserProxyBootstrap;
import org.geysermc.connect.storage.AbstractStorageManager;
import org.geysermc.connect.storage.DisabledStorageManager;
import org.geysermc.connect.utils.*;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
public class MasterServer {
private BedrockServer bdServer;
@Getter
private boolean shuttingDown = false;
@Getter
private static MasterServer instance;
@Getter
private final Logger logger;
@Getter
private final ScheduledExecutorService generalThreadPool;
@Getter
private final List<Player> players = new ObjectArrayList<>();
@Getter
private GeyserProxyBootstrap geyserProxy;
@Getter
private GeyserConnectConfig geyserConnectConfig;
@Getter
private AbstractStorageManager storageManager;
@Getter
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(new DefaultThreadFactory("Geyser player thread"));
public MasterServer() {
instance = this;
logger = new Logger();
try {
File configFile = GeyserConnectFileUtils.fileOrCopiedFromResource(new File("config.yml"), "config.yml", (x) -> x);
this.geyserConnectConfig = FileUtils.loadConfig(configFile, GeyserConnectConfig.class);
} catch (IOException ex) {
logger.severe("Failed to read/create config.yml! Make sure it's up to date and/or readable+writable!", ex);
ex.printStackTrace();
}
logger.setDebug(geyserConnectConfig.isDebugMode());
// As this is only used for fixing the form image bug, we don't need to handle many threads
this.generalThreadPool = Executors.newSingleThreadScheduledExecutor();
// Start a timer to keep the thread running
Timer timer = new Timer();
TimerTask task = new TimerTask() { public void run() { } };
timer.scheduleAtFixedRate(task, 0L, 1000L);
if (!geyserConnectConfig.getCustomServers().isEnabled()) {
// Force the storage manager if we have it disabled
storageManager = new DisabledStorageManager();
logger.info("Disabled custom player servers");
} else {
try {
storageManager = geyserConnectConfig.getCustomServers().getStorageType().getStorageManager().newInstance();
} catch (Exception e) {
logger.severe("Invalid storage manager class!", e);
return;
}
}
storageManager.setupStorage();
// Create the base welcome.txt file
try {
GeyserConnectFileUtils.fileOrCopiedFromResource(new File(getGeyserConnectConfig().getWelcomeFile()), "welcome.txt", (x) -> x);
} catch (IOException ignored) { }
start(geyserConnectConfig.getPort());
logger.start();
}
private void start(int port) {
logger.info("Starting...");
InetSocketAddress bindAddress = new InetSocketAddress(geyserConnectConfig.getAddress(), port);
bdServer = new BedrockServer(bindAddress);
bdServer.setHandler(new BedrockServerEventHandler() {
@Override
public boolean onConnectionRequest(InetSocketAddress address) {
return true; // Connection will be accepted
}
@Override
public BedrockPong onQuery(InetSocketAddress address) {
int playerCount = players.size() + GeyserImpl.getInstance().getSessionManager().size();
String subMotd = geyserConnectConfig.getSubmotd();
if (subMotd == null || subMotd.isEmpty()) {
subMotd = "GeyserConnect";
}
BedrockPong bdPong = new BedrockPong();
bdPong.setEdition("MCPE");
bdPong.setMotd(geyserConnectConfig.getMotd());
bdPong.setSubMotd(subMotd);
bdPong.setPlayerCount(playerCount);
bdPong.setMaximumPlayerCount(geyserConnectConfig.getMaxPlayers());
bdPong.setGameType("Survival");
bdPong.setIpv4Port(port);
bdPong.setProtocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
bdPong.setVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion());
return bdPong;
}
@Override
public void onSessionCreation(BedrockServerSession session) {
session.setPacketHandler(new PacketHandler(session, instance));
}
});
// Start server up
bdServer.bind().join();
// Create the Geyser instance
createGeyserProxy();
logger.info("Server started on " + geyserConnectConfig.getAddress() + ":" + port);
}
public void shutdown() {
shuttingDown = true;
bdServer.close();
shutdownGeyserProxy();
generalThreadPool.shutdown();
storageManager.closeStorage();
System.exit(0);
}
public void createGeyserProxy() {
if (geyserProxy == null) {
// Make sure Geyser doesn't start the listener
GeyserImpl.setShouldStartListener(false);
this.geyserProxy = new GeyserProxyBootstrap();
geyserProxy.onEnable();
GeyserImpl.getInstance().getPendingMicrosoftAuthentication().setStoreServerInformation();
}
}
public void shutdownGeyserProxy() {
if (geyserProxy != null) {
geyserProxy.onDisable();
geyserProxy = null;
}
}
public List<Server> getServers(ServerCategory serverCategory) {
return getGeyserConnectConfig().getServers().stream().filter(server -> server.getCategory() == serverCategory).collect(Collectors.toList());
}
}

View file

@ -1,427 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nimbusds.jose.shaded.json.JSONArray;
import com.nukkitx.network.util.DisconnectReason;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.ExperimentData;
import com.nukkitx.protocol.bedrock.data.PacketCompressionAlgorithm;
import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.connect.proxy.GeyserProxySession;
import org.geysermc.connect.ui.FormID;
import org.geysermc.connect.ui.UIHandler;
import org.geysermc.connect.utils.GeyserConnectFileUtils;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.response.CustomFormResponse;
import org.geysermc.cumulus.response.FormResponse;
import org.geysermc.cumulus.response.SimpleFormResponse;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication.*;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.geyser.session.auth.BedrockClientData;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.security.interfaces.ECPublicKey;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class PacketHandler implements BedrockPacketHandler {
private final BedrockServerSession session;
private final MasterServer masterServer;
private Player player;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public PacketHandler(BedrockServerSession session, MasterServer masterServer) {
this.session = session;
this.masterServer = masterServer;
session.addDisconnectHandler(this::disconnect);
}
public void disconnect(DisconnectReason reason) {
if (player != null) {
masterServer.getLogger().info(player.getAuthData().name() + " has disconnected from the master server (" + reason + ")");
masterServer.getStorageManager().saveServers(player);
masterServer.getPlayers().remove(player);
}
}
private boolean checkedProtocol = false;
@Override
public boolean handle(RequestNetworkSettingsPacket packet) {
if (checkProtocol(packet.getProtocolVersion())) {
PacketCompressionAlgorithm algorithm = PacketCompressionAlgorithm.ZLIB;
NetworkSettingsPacket responsePacket = new NetworkSettingsPacket();
responsePacket.setCompressionAlgorithm(algorithm);
responsePacket.setCompressionThreshold(512);
session.sendPacketImmediately(responsePacket);
session.setCompression(algorithm);
}
return true;
}
private boolean checkProtocol(int protocolVersion) {
BedrockPacketCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
if (packetCodec == null) {
session.setPacketCodec(GameProtocol.DEFAULT_BEDROCK_CODEC);
String message = "disconnectionScreen.internalError.cantConnect";
PlayStatusPacket status = new PlayStatusPacket();
if (protocolVersion > GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_SERVER_OLD);
message = "disconnectionScreen.outdatedServer";
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
status.setStatus(PlayStatusPacket.Status.LOGIN_FAILED_CLIENT_OLD);
message = "disconnectionScreen.outdatedClient";
}
session.sendPacket(status);
session.disconnect(message);
return false;
}
// Set the session codec
session.setPacketCodec(packetCodec);
return true;
}
@Override
public boolean handle(LoginPacket packet) {
masterServer.getLogger().debug("Login: " + packet.toString());
if (!checkedProtocol) {
if (!checkProtocol(packet.getProtocolVersion())) {
return false;
}
checkedProtocol = true;
}
// Read the raw chain data
JsonNode rawChainData;
try {
rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray());
} catch (IOException e) {
throw new AssertionError("Unable to read chain data!");
}
// Get the parsed chain data
JsonNode chainData = rawChainData.get("chain");
if (chainData.getNodeType() != JsonNodeType.ARRAY) {
throw new AssertionError("Invalid chain data!");
}
try {
// Convert the chainData to a JSONArray
ObjectReader reader = OBJECT_MAPPER.readerFor(new TypeReference<List<String>>() { });
JSONArray array = new JSONArray();
array.addAll(reader.readValue(chainData));
// Verify the chain data
if (!EncryptionUtils.verifyChain(array)) {
// Disconnect the client
session.disconnect("disconnectionScreen.internalError.cantConnect");
throw new AssertionError("Failed to login, due to invalid chain data!");
}
// Parse the signed jws object
JWSObject jwsObject;
jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText());
// Read the JWS payload
JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes());
// Check the identityPublicKey is there
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
throw new AssertionError("Missing identity public key!");
}
// Create an ECPublicKey from the identityPublicKey
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
// Get the skin data to validate the JWS token
JWSObject skinData = JWSObject.parse(packet.getSkinData().toString());
if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) {
// Make sure the client sent over the username, xuid and other info
if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) {
throw new AssertionError("Missing client data");
}
// Fetch the client data
JsonNode extraData = payload.get("extraData");
AuthData authData = new AuthData(
extraData.get("displayName").asText(),
UUID.fromString(extraData.get("identity").asText()),
extraData.get("XUID").asText()
);
// Create a new player and add it to the players list
player = new Player(authData, session);
masterServer.getPlayers().add(player);
player.setChainData(chainData);
// Store the full client data
player.setClientData(OBJECT_MAPPER.convertValue(OBJECT_MAPPER.readTree(skinData.getPayload().toBytes()), BedrockClientData.class));
player.getClientData().setOriginalString(packet.getSkinData().toString());
// Tell the client we have logged in successfully
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
session.sendPacket(playStatusPacket);
// Tell the client there are no resourcepacks
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
session.sendPacket(resourcePacksInfo);
} else {
throw new AssertionError("Invalid identity public key!");
}
} catch (Exception e) {
// Disconnect the client
session.disconnect("disconnectionScreen.internalError.cantConnect");
throw new AssertionError("Failed to login", e);
}
return false;
}
@Override
public boolean handle(ResourcePackClientResponsePacket packet) {
switch (packet.getStatus()) {
case COMPLETED:
masterServer.getLogger().info("Logged in " + player.getAuthData().name() + " (" + player.getAuthData().xuid() + ", " + player.getAuthData().uuid() + ")");
ProxyAuthenticationTask task = (ProxyAuthenticationTask) GeyserImpl.getInstance()
.getPendingMicrosoftAuthentication().getTask(player.getAuthData().xuid());
if (task != null && task.getAuthentication().isDone()) {
String address = task.getServer();
int port = task.getPort();
player.setCurrentServer(new Server(address, port, true, false));
GeyserProxySession session = player.createGeyserSession(false);
session.remoteServer(player.getCurrentServer());
session.onMicrosoftLoginComplete(task);
} else {
player.sendStartGame();
}
break;
case HAVE_ALL_PACKS:
ResourcePackStackPacket stack = new ResourcePackStackPacket();
stack.setExperimentsPreviouslyToggled(false);
stack.setForcedToAccept(false);
stack.setGameVersion("*");
if (!Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion()).getComponentItemData().isEmpty()) {
// Allow custom items to work
stack.getExperiments().add(new ExperimentData("data_driven_items", true));
}
if (session.getPacketCodec().getProtocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) {
// Allow extended world height in the overworld to work for pre-1.18 clients
stack.getExperiments().add(new ExperimentData("caves_and_cliffs", true));
}
session.sendPacket(stack);
break;
default:
session.disconnect("disconnectionScreen.resourcePack");
break;
}
return true;
}
@Override
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
masterServer.getLogger().debug("Player initialized: " + player.getAuthData().name());
if (player.getCurrentServer() != null) {
// Player is already logged in via delayed Microsoft authentication
return false;
}
// Handle the virtual host if specified
GeyserConnectConfig.VirtualHostSection vhost = MasterServer.getInstance().getGeyserConnectConfig().getVhost();
if (vhost.isEnabled()) {
String domain = player.getClientData().getServerAddress().split(":")[0];
if (!domain.equals(vhost.getBaseDomain()) && domain.endsWith("." + vhost.getBaseDomain())) {
String address = "";
int port = 25565;
boolean online = true;
// Parse the address used
String[] domainParts = domain.replaceFirst("\\." + vhost.getBaseDomain() + "$", "").split("\\._");
for (int i = 0; i < domainParts.length; i++) {
String part = domainParts[i];
if (i == 0) {
address = part;
} else if (part.startsWith("p")) {
port = Integer.parseInt(part.substring(1));
} else if (part.startsWith("o")) {
online = false;
}
}
// They didn't specify an address so disconnect them
if (address.startsWith("_")) {
session.disconnect("disconnectionScreen.invalidIP");
return false;
}
// Log the virtual host usage
masterServer.getLogger().info(player.getAuthData().name() + " is using virtualhost: " + address + ":" + port + (!online ? " (offline)" : ""));
// Send the player to the wanted server
player.sendToServer(new Server(address, port, online, false));
return false;
}
}
String message = "";
try {
File messageFile = GeyserConnectFileUtils.fileOrCopiedFromResource(new File(MasterServer.getInstance().getGeyserConnectConfig().getWelcomeFile()), "welcome.txt", (x) -> x);
message = new String(FileUtils.readAllBytes(messageFile));
} catch (IOException ignored) { }
if (!message.trim().isEmpty()) {
player.sendWindow(FormID.WELCOME, UIHandler.getMessageWindow(message));
} else {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
}
return false;
}
@Override
public boolean handle(ModalFormResponsePacket packet) {
// Make sure the form is valid
FormID id = FormID.fromId(packet.getFormId());
if (id != player.getCurrentWindowId())
return false;
// Fetch the form and parse the response
Form window = player.getCurrentWindow();
FormResponse response = window.parseResponse(packet.getFormData() == null ? null : packet.getFormData().trim());
// Resend the form if they closed it
if (!response.isCorrect() && !id.isHandlesNull()) {
player.resendWindow();
} else {
// Send the response to the correct response function
switch (id) {
case WELCOME:
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
break;
case MAIN:
UIHandler.handleMainMenuResponse(player, (SimpleFormResponse) response);
break;
case LIST_SERVERS:
UIHandler.handleServerListResponse(player, (SimpleFormResponse) response);
break;
case DIRECT_CONNECT:
UIHandler.handleDirectConnectResponse(player, (CustomFormResponse) response);
break;
case EDIT_SERVERS:
UIHandler.handleEditServerListResponse(player, (SimpleFormResponse) response);
break;
case ADD_SERVER:
UIHandler.handleAddServerResponse(player, (CustomFormResponse) response);
break;
case SERVER_OPTIONS:
UIHandler.handleServerOptionsResponse(player, (SimpleFormResponse) response);
break;
case REMOVE_SERVER:
UIHandler.handleServerRemoveResponse(player, (SimpleFormResponse) response);
break;
case EDIT_SERVER:
UIHandler.handleEditServerResponse(player, (CustomFormResponse) response);
break;
default:
player.resendWindow();
break;
}
}
return true;
}
@Override
public boolean handle(NetworkStackLatencyPacket packet) {
// This is to fix a bug in the client where it doesn't load form images
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
updateAttributesPacket.setRuntimeEntityId(1);
List<AttributeData> attributes = Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0f));
updateAttributesPacket.setAttributes(attributes);
// Doesn't work 100% of the time but fixes it most of the time
MasterServer.getInstance().getGeneralThreadPool().schedule(() -> session.sendPacket(updateAttributesPacket), 500, TimeUnit.MILLISECONDS);
return false;
}
}

View file

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

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.extension;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacketHandler;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.cloudburstmc.protocol.common.PacketSignal;
import org.geysermc.connect.extension.ui.UIHandler;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.network.UpstreamPacketHandler;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.DimensionUtils;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class PacketHandler extends UpstreamPacketHandler {
private final GeyserSession session;
private final GeyserConnect geyserConnect;
private final BedrockPacketHandler originalPacketHandler;
public PacketHandler(GeyserConnect geyserConnect, GeyserSession session, BedrockPacketHandler packetHandler) {
super(GeyserImpl.getInstance(), session);
this.session = session;
this.geyserConnect = geyserConnect;
this.originalPacketHandler = packetHandler;
// Spawn the player in the end (it just looks better)
session.setDimension(DimensionUtils.THE_END);
DimensionUtils.setBedrockDimension(session, DimensionUtils.THE_END);
}
@Override
public void onDisconnect(String reason) {
geyserConnect.logger().info(Utils.displayName(session) + " has disconnected (" + reason + ")");
ServerManager.unloadServers(session);
}
@Override
public PacketSignal handle(SetLocalPlayerAsInitializedPacket packet) {
if (session.getPlayerEntity().getGeyserId() == packet.getRuntimeEntityId()) {
if (!session.getUpstream().isInitialized()) {
session.getUpstream().setInitialized(true);
// Load the players servers
ServerManager.loadServers(session);
geyserConnect.logger().debug("Player initialized: " + Utils.displayName(session));
UIHandler uiHandler = new UIHandler(session, packet, originalPacketHandler);
uiHandler.initialiseSession();
}
}
// Handle the virtual host if specified
// GeyserConnectConfig.VirtualHostSection vhost = MasterServer.getInstance().getGeyserConnectConfig().getVhost();
// if (vhost.isEnabled()) {
// String domain = player.getClientData().getServerAddress().split(":")[0];
// if (!domain.equals(vhost.getBaseDomain()) && domain.endsWith("." + vhost.getBaseDomain())) {
// String address = "";
// int port = 25565;
// boolean online = true;
//
// // Parse the address used
// String[] domainParts = domain.replaceFirst("\\." + vhost.getBaseDomain() + "$", "").split("\\._");
// for (int i = 0; i < domainParts.length; i++) {
// String part = domainParts[i];
// if (i == 0) {
// address = part;
// } else if (part.startsWith("p")) {
// port = Integer.parseInt(part.substring(1));
// } else if (part.startsWith("o")) {
// online = false;
// }
// }
//
// // They didn't specify an address so disconnect them
// if (address.startsWith("_")) {
// session.disconnect("disconnectionScreen.invalidIP");
// return false;
// }
//
// // Log the virtual host usage
// masterServer.getLogger().info(player.getAuthData().name() + " is using virtualhost: " + address + ":" + port + (!online ? " (offline)" : ""));
//
// // Send the player to the wanted server
// player.sendToServer(new Server(address, port, online, false));
//
// return false;
// }
// }
return PacketSignal.HANDLED;
}
@Override
public PacketSignal handle(NetworkStackLatencyPacket packet) {
// This is to fix a bug in the client where it doesn't load form images
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
updateAttributesPacket.setRuntimeEntityId(1);
List<AttributeData> attributes = Collections.singletonList(GeyserAttributeType.EXPERIENCE_LEVEL.getAttribute(0f));
updateAttributesPacket.setAttributes(attributes);
// Doesn't work 100% of the time but fixes it most of the time
GeyserImpl.getInstance().getScheduledThread().schedule(() -> session.sendUpstreamPacket(updateAttributesPacket), 500, TimeUnit.MILLISECONDS);
return super.handle(packet);
}
}

View file

@ -23,11 +23,16 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect;
package org.geysermc.connect.extension.config;
public class GeyserConnect {
import com.fasterxml.jackson.annotation.JsonProperty;
import org.geysermc.connect.extension.utils.Server;
public static void main(String[] args) {
new MasterServer();
}
import java.util.List;
public record Config(
@JsonProperty("welcome-file") String welcomeFile,
List<Server> servers,
@JsonProperty("custom-servers") CustomServersSection customServers,
VirtualHostSection vhost) {
}

View file

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

View file

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

View file

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

View file

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

View file

@ -23,22 +23,26 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.utils.Server;
import org.geysermc.connect.extension.utils.ServerManager;
import org.geysermc.connect.extension.utils.Utils;
import org.geysermc.geyser.session.GeyserSession;
import java.io.IOException;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
private final ObjectMapper mapper = new ObjectMapper();
protected Connection connection;
@Override
@ -49,8 +53,19 @@ public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
try (Statement createPlayersTable = connection.createStatement()) {
createPlayersTable.executeUpdate("CREATE TABLE IF NOT EXISTS players (xuid VARCHAR(32), servers TEXT, PRIMARY KEY(xuid));");
}
try (PreparedStatement getPlayersServers = connection.prepareStatement("SELECT xuid, servers FROM players")) {
ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) {
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(rs.getString("servers"), new TypeReference<>() {});
GeyserConnect.instance().logger().info("Loaded " + loadedServers.size() + " servers for " + rs.getString("xuid"));
}
} catch (IOException | SQLException exception) {
GeyserConnect.instance().logger().error("Couldn't load servers", exception);
}
} catch (ClassNotFoundException | SQLException e) {
MasterServer.getInstance().getLogger().severe("Unable to connect to MySQL database!", e);
GeyserConnect.instance().logger().severe("Unable to connect to MySQL database!", e);
}
}
@ -61,37 +76,38 @@ public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
try {
connection.close();
} catch (SQLException exception) {
MasterServer.getInstance().getLogger().error("Failed to close SQL connection", exception);
GeyserConnect.instance().logger().error("Failed to close SQL connection", exception);
}
}
@Override
public void saveServers(Player player) {
public void saveServers(GeyserSession session) {
// replace into works on MySQL and SQLite
try (PreparedStatement updatePlayersServers = connection.prepareStatement("REPLACE INTO players(xuid, servers) VALUES(?, ?)")) {
updatePlayersServers.setString(1, player.getAuthData().xuid());
updatePlayersServers.setString(2, mapper.writeValueAsString(player.getServers()));
updatePlayersServers.setString(1, session.getAuthData().xuid());
updatePlayersServers.setString(2, Utils.OBJECT_MAPPER.writeValueAsString(ServerManager.getServers(session)));
updatePlayersServers.executeUpdate();
} catch (IOException | SQLException exception) {
MasterServer.getInstance().getLogger().error("Couldn't save servers for " + player.getAuthData().name(), exception);
GeyserConnect.instance().logger().error("Couldn't save servers for " + session.getAuthData().name(), exception);
}
}
@Override
public List<Server> loadServers(Player player) {
public List<Server> loadServers(GeyserSession session) {
List<Server> servers = new ArrayList<>();
try (PreparedStatement getPlayersServers = connection.prepareStatement("SELECT servers FROM players WHERE xuid=?")) {
getPlayersServers.setString(1, player.getAuthData().xuid());
getPlayersServers.setString(1, session.getAuthData().xuid());
ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) {
List<Server> loadedServers = mapper.readValue(rs.getString("servers"), new TypeReference<>() {
});
List<Server> loadedServers = Utils.OBJECT_MAPPER.readValue(rs.getString("servers"), new TypeReference<>() {});
if (loadedServers != null) {
servers.addAll(loadedServers);
}
}
} catch (IOException | SQLException exception) {
MasterServer.getInstance().getLogger().error("Couldn't load servers for " + player.getAuthData().name(), exception);
GeyserConnect.instance().logger().error("Couldn't load servers for " + session.getAuthData().name(), exception);
}
return servers;

View file

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

View file

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

View file

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

View file

@ -23,18 +23,19 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import org.geysermc.connect.GeyserConnectConfig;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.extension.config.MySQLConnectionSection;
import java.sql.*;
import java.sql.DriverManager;
import java.sql.SQLException;
public class MySQLStorageManager extends AbstractSQLStorageManager {
@Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
GeyserConnectConfig.MySQLConnectionSection connectionInformation = MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().getMysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.getHost() + ":" + connectionInformation.getPort() + "/" + connectionInformation.getDatabase(), connectionInformation.getUser(), connectionInformation.getPass());
MySQLConnectionSection connectionInformation = GeyserConnect.instance().config().customServers().mysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.host() + ":" + connectionInformation.port() + "/" + connectionInformation.database(), connectionInformation.user(), connectionInformation.pass());
}
}

View file

@ -23,14 +23,17 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.storage;
package org.geysermc.connect.extension.storage;
import java.sql.*;
import org.geysermc.connect.extension.GeyserConnect;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLiteStorageManager extends AbstractSQLStorageManager {
@Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:players.db");
connection = DriverManager.getConnection("jdbc:sqlite:" + GeyserConnect.instance().dataFolder().resolve("players.db"));
}
}

View file

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

View file

@ -23,80 +23,32 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
package org.geysermc.connect.extension.utils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.cumulus.util.FormImage;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer;
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Server implements RemoteServer {
private String address;
private int port = -1;
private boolean online = true;
private boolean bedrock = false;
private String name = null;
private String imageUrl = null;
private ServerCategory category = null;
public Server(String address) {
this(address, -1);
}
public record Server(
String address,
int port,
boolean online,
boolean bedrock,
String name,
String imageUrl,
ServerCategory category
) implements RemoteServer {
public Server(String address, int port) {
this(address, port, true);
}
public Server(String address, int port, boolean online) {
this(address, port, online, false);
}
public Server(String address, int port, boolean online, boolean bedrock) {
this(address, port, online, bedrock, null);
}
public Server(String address, int port, boolean online, boolean bedrock, String name) {
this(address, port, online, bedrock, name, null);
}
public Server(String address, int port, boolean online, boolean bedrock, String name, String imageUrl) {
this(address.replaceAll(" ", ""), port, online, bedrock, name, imageUrl, ServerCategory.CUSTOM);
this(address, port, true, false, null, null, ServerCategory.CUSTOM);
}
private int defaultPort() { return bedrock ? 19132 : 25565; }
public int getPort() { return port < 0 ? defaultPort() : port; }
@Override
public String toString() {
return name != null ? name : address + (getPort() != defaultPort() ? ":" + getPort() : "");
}
@JsonIgnore
public FormImage getFormImage() {
if (imageUrl != null && !imageUrl.isEmpty()) {
return FormImage.of(FormImage.Type.URL, imageUrl);
} else {
return FormImage.of(FormImage.Type.URL, "https://eu.mc-api.net/v3/server/favicon/" + address + ":" + port + ".png?use-fallback-icon=true");
}
}
@Override
public String address() {
return address;
}
@Override
public int port() {
return port;
return port < 0 ? defaultPort() : port;
}
@Override
@ -104,6 +56,15 @@ public class Server implements RemoteServer {
return this.online ? AuthType.ONLINE : AuthType.OFFLINE;
}
@JsonIgnore
public FormImage formImage() {
if (imageUrl != null && !imageUrl.isEmpty()) {
return FormImage.of(FormImage.Type.URL, imageUrl);
} else {
return FormImage.of(FormImage.Type.URL, "https://eu.mc-api.net/v3/server/favicon/" + address + ":" + port + ".png?use-fallback-icon=true");
}
}
@Override
public String minecraftVersion() {
return null;
@ -113,4 +74,8 @@ public class Server implements RemoteServer {
public int protocolVersion() {
return 0;
}
public String title() {
return name != null ? name : address + (port() != defaultPort() ? ":" + port() : "");
}
}

View file

@ -23,11 +23,8 @@
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
package org.geysermc.connect.extension.utils;
import lombok.Getter;
@Getter
public enum ServerCategory {
OFFICIAL("Official"),
GEYSER("Geyser"),
@ -38,4 +35,8 @@ public enum ServerCategory {
ServerCategory(String title) {
this.title = title;
}
public String title() {
return title;
}
}

View file

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

View file

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

View file

@ -1,141 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.proxy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.geysermc.common.PlatformType;
import org.geysermc.connect.GeyserConnectConfig;
import org.geysermc.connect.MasterServer;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.text.GeyserLocale;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
public class GeyserProxyBootstrap implements GeyserBootstrap {
private GeyserCommandManager geyserCommandManager;
private GeyserProxyConfiguration geyserConfig;
private GeyserProxyLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserImpl geyser;
@Override
public void onEnable() {
GeyserLocale.init(this);
// Setup a logger
geyserLogger = new GeyserProxyLogger();
// Read the static config from resources
try {
InputStream configFile = GeyserProxyBootstrap.class.getClassLoader().getResourceAsStream("proxy_config.yml");
// Grab the config as text and replace static strings to the main config variables
String text = new BufferedReader(new InputStreamReader(configFile, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n"));
GeyserConnectConfig multiConfig = MasterServer.getInstance().getGeyserConnectConfig();
text = text.replaceAll("%MOTD%", multiConfig.getMotd());
text = text.replace("%PLAYERS%", String.valueOf(multiConfig.getMaxPlayers()));
text = text.replace("%ALLOWPASSWORDAUTHENTICATION%", String.valueOf(multiConfig.getGeyser().isAllowPasswordAuthentication()));
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
geyserConfig = objectMapper.readValue(text, GeyserProxyConfiguration.class);
geyserConfig.getSavedUserLogins().clear();
for (String savedUserLogin : MasterServer.getInstance().getGeyserConnectConfig().getGeyser().getSavedUserLogins()) {
geyserConfig.getSavedUserLogins().add(savedUserLogin);
}
} catch (IOException ex) {
geyserLogger.severe("Failed to read proxy_config.yml! Make sure it's up to date and/or readable+writable!", ex);
return;
}
// Not sure there is a point in doing this as its a static config
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Create the connector and command manager
geyser = GeyserImpl.load(PlatformType.STANDALONE, this);
GeyserImpl.start();
geyserCommandManager = new GeyserCommandManager(geyser);
// Start the ping passthrough thread, again don't think there is a point
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
// Swap the normal handler to our custom handler so we can change some
geyser.getBedrockServer().setHandler(new ProxyConnectorServerEventHandler(geyser));
}
@Override
public void onDisable() {
geyser.shutdown();
}
@Override
public GeyserConfiguration getGeyserConfig() {
return geyserConfig;
}
@Override
public GeyserProxyLogger getGeyserLogger() {
return geyserLogger;
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
}
@Override
public IGeyserPingPassthrough getGeyserPingPassthrough() {
return geyserPingPassthrough;
}
@Override
public Path getConfigFolder() {
return Paths.get(System.getProperty("user.dir"));
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new BootstrapDumpInfo();
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.proxy;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import java.nio.file.Path;
import java.nio.file.Paths;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GeyserProxyConfiguration extends GeyserJacksonConfiguration {
@Override
public Path getFloodgateKeyPath() {
return Paths.get(getFloodgateKeyFile());
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.proxy;
import lombok.extern.log4j.Log4j2;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Logger;
@Log4j2
public class GeyserProxyLogger extends Logger {
/**
* Disable debug messages depending on config
*/
public void debug(String message) {
if (MasterServer.getInstance().getGeyserConnectConfig().getGeyser().isDebugMode())
super.debug(message);
}
}

View file

@ -1,60 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.proxy;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import io.netty.channel.EventLoop;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication.*;
public class GeyserProxySession extends GeyserSession {
private final Player player;
public GeyserProxySession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop, Player player, boolean initialized) {
super(geyser, bedrockServerSession, eventLoop);
sentSpawnPacket = initialized;
this.player = player;
}
@Override
protected void disableSrvResolving() {
// Do nothing
}
@Override
public void disconnect(String reason) {
ProxyAuthenticationTask task = (ProxyAuthenticationTask) getGeyser().getPendingMicrosoftAuthentication().getTask(this.xuid());
if (task != null) {
Server server = player.getCurrentServer();
task.setServer(server.getAddress());
task.setPort(server.getPort());
}
super.disconnect(reason);
}
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.proxy;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.ConnectorServerEventHandler;
public class ProxyConnectorServerEventHandler extends ConnectorServerEventHandler {
public ProxyConnectorServerEventHandler(GeyserImpl geyser) {
super(geyser);
}
@Override
public void onSessionCreation(BedrockServerSession bedrockServerSession) {
super.onSessionCreation(bedrockServerSession);
bedrockServerSession.disconnect("Not sure how you managed it, but you shouldn't be here!");
}
}

View file

@ -1,60 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.ui;
import lombok.Getter;
@Getter
public enum FormID {
WELCOME,
MAIN,
DIRECT_CONNECT(true),
LIST_SERVERS(true),
EDIT_SERVERS(true),
SERVER_OPTIONS(true),
ADD_SERVER(true),
REMOVE_SERVER,
EDIT_SERVER(true),
CONNECTING,
ERROR;
private final boolean handlesNull;
private static final FormID[] VALUES = values();
FormID() {
this(false);
}
FormID(boolean handlesNull) {
this.handlesNull = handlesNull;
}
public static FormID fromId(int id) {
return id >= 0 && id < VALUES.length ? VALUES[id] : ERROR;
}
}

View file

@ -1,494 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.ui;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.utils.Player;
import org.geysermc.connect.utils.Server;
import org.geysermc.connect.utils.ServerCategory;
import org.geysermc.cumulus.CustomForm;
import org.geysermc.cumulus.Form;
import org.geysermc.cumulus.SimpleForm;
import org.geysermc.cumulus.component.InputComponent;
import org.geysermc.cumulus.component.LabelComponent;
import org.geysermc.cumulus.component.ToggleComponent;
import org.geysermc.cumulus.response.CustomFormResponse;
import org.geysermc.cumulus.response.SimpleFormResponse;
import org.geysermc.cumulus.util.FormImage;
import java.util.List;
public class UIHandler {
/**
* Create a list of servers for the client based on the passed servers list
*
* @return A {@link SimpleForm} object
*/
public static Form getMainMenu() {
SimpleForm.Builder window = SimpleForm.builder().title("Main Menu");
window.button("Official Servers");
window.button("Geyser Servers");
// Add a buttons for custom servers
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
window.button("Custom Servers");
window.button("Direct connect");
}
window.button("Disconnect");
return window.build();
}
/**
* Create a list of servers for the client based on the passed servers list
*
* @param servers A list of {@link Server} objects
* @param category The category of the current list
* @return A {@link SimpleForm} object
*/
public static Form getServerList(List<Server> servers, ServerCategory category) {
SimpleForm.Builder window = SimpleForm.builder().title(category.getTitle() + " Servers");
// Add a button for each global server
for (Server server : servers) {
// These images would be better if there was a default to fall back on
// But that would require a web api as bedrock doesn't support doing that
window.button(server.toString(), server.getFormImage());
}
// Add a button for editing
if (category == ServerCategory.CUSTOM) {
window.button("Edit servers");
}
window.button("Back");
return window.build();
}
/**
* Create a direct connect form
*
* @return A {@link CustomForm} object
*/
public static Form getDirectConnect() {
return CustomForm.builder().title("Direct Connect")
.component(InputComponent.of("IP", "play.cubecraft.net"))
.component(InputComponent.of("Port", "25565", "25565"))
.component(ToggleComponent.of("Online mode", true))
.component(ToggleComponent.of("Bedrock/Geyser server", false))
.build();
}
/**
* Create a list of servers for the client to edit
*
* @param servers A list of {@link Server} objects
* @return A {@link SimpleForm} object
*/
public static Form getEditServerList(List<Server> servers) {
SimpleForm.Builder window = SimpleForm.builder().title("Edit Servers").content("Select a server to edit");
// Add a button for each personal server
for (Server server : servers) {
window.button(server.toString(), FormImage.of(FormImage.Type.URL,
"https://eu.mc-api.net/v3/server/favicon/" + server.getAddress() + ":" + server.getPort() + ".png"));
}
window.button("Add server");
window.button("Back");
return window.build();
}
/**
* Create a add server form
*
* @return A {@link CustomForm} object
*/
public static Form getAddServer() {
return CustomForm.builder().title("Add Server")
.component(InputComponent.of("IP", "play.cubecraft.net"))
.component(InputComponent.of("Port", "25565", "25565"))
.component(ToggleComponent.of("Online mode", true))
.component(ToggleComponent.of("Bedrock/Geyser server", false))
.build();
}
/**
* Create a server options form
*
* @param server A {@link Server} object to show options for
* @return A {@link SimpleForm} object
*/
public static Form getServerOptions(Server server) {
SimpleForm.Builder window = SimpleForm.builder().title("Server Options").content(server.toString());
window.button("Edit");
window.button("Remove");
window.button("Back");
return window.build();
}
/**
* Create a remove server form
*
* @param server A {@link Server} object to remove
* @return A {@link SimpleForm} object
*/
public static Form getRemoveServer(Server server) {
return SimpleForm.builder()
.title("Remove Server")
.content("Are you sure you want to remove server: " + server)
.button("Remove")
.button("Cancel")
.build();
}
/**
* Create a edit server form
*
* @param server A {@link Server} object to edit
* @return A {@link CustomForm} object
*/
public static Form getEditServer(int serverIndex, Server server) {
String port = String.valueOf(server.getPort());
return CustomForm.builder()
.component(LabelComponent.of("Server at index: " + serverIndex))
.component(InputComponent.of("IP", server.getAddress(), server.getAddress()))
.component(InputComponent.of("Port", port, port))
.component(ToggleComponent.of("Online mode", server.isOnline()))
.component(ToggleComponent.of("Bedrock/Geyser server", server.isBedrock()))
.build();
}
/**
* Show a basic form window with a message
*
* @param message The message to display
* @return A {@link CustomForm} object
*/
public static Form getMessageWindow(String message) {
return CustomForm.builder()
.title("Notice")
.component(LabelComponent.of(message))
.build();
}
/**
* Handle the main menu response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleMainMenuResponse(Player player, SimpleFormResponse data) {
switch (data.getClickedButtonId()) {
case 0:
player.setServerCategory(ServerCategory.OFFICIAL);
break;
case 1:
player.setServerCategory(ServerCategory.GEYSER);
break;
default:
// If we have custom servers enabled there are a few extra buttons
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
switch (data.getClickedButtonId()) {
case 2:
player.setServerCategory(ServerCategory.CUSTOM);
break;
case 3:
player.sendWindow(FormID.DIRECT_CONNECT, getDirectConnect());
return;
default:
player.getSession().disconnect("disconnectionScreen.disconnected");
return;
}
} else {
player.getSession().disconnect("disconnectionScreen.disconnected");
return;
}
break;
}
// Send the server list
player.sendWindow(FormID.LIST_SERVERS, getServerList(player.getCurrentServers(), player.getServerCategory()));
}
/**
* Handle the server list response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerListResponse(Player player, SimpleFormResponse data) {
List<Server> servers = player.getCurrentServers();
if (player.getServerCategory() == ServerCategory.CUSTOM) {
if (!data.isCorrect() || data.getClickedButtonId() == servers.size() + 1) {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
} else if (data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getCurrentServers()));
} else {
// Get the server
Server server = servers.get(data.getClickedButtonId());
player.sendToServer(server);
}
} else {
if (!data.isCorrect() || data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.MAIN, UIHandler.getMainMenu());
} else {
// Get the server
Server server = servers.get(data.getClickedButtonId());
player.sendToServer(server);
}
}
}
/**
* Handle the direct connect response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleDirectConnectResponse(Player player, CustomFormResponse data) {
// Take them back to the main menu if they close the direct connect window
if (!data.isCorrect()) {
player.sendWindow(FormID.MAIN, getMainMenu());
return;
}
try {
String address = data.getInput(0);
int port = Integer.parseInt(data.getInput(1));
boolean online = data.getToggle(2);
boolean bedrock = data.getToggle(3);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.MAIN, getMainMenu());
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.sendToServer(new Server(address, port, online, bedrock));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
/**
* Handle the edit server list response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleEditServerListResponse(Player player, SimpleFormResponse data) {
List<Server> servers = player.getCurrentServers();
// Take them back to the main menu if they close the edit server list window
if (!data.isCorrect()) {
player.sendWindow(FormID.LIST_SERVERS, getServerList(servers, player.getServerCategory()));
return;
}
if (data.getClickedButtonId() == servers.size()) {
player.sendWindow(FormID.ADD_SERVER, getAddServer());
} else if (data.getClickedButtonId() == servers.size() + 1) {
player.sendWindow(FormID.LIST_SERVERS, getServerList(servers, player.getServerCategory()));
} else {
Server server = player.getServers().get(data.getClickedButtonId());
player.sendWindow(FormID.SERVER_OPTIONS, getServerOptions(server));
}
}
/**
* Handle the add server response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleAddServerResponse(Player player, CustomFormResponse data) {
// Take them back to the edit server list menu if they close the add server window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
try {
String address = data.getInput(0);
int port = Integer.parseInt(data.getInput(1));
boolean online = data.getToggle(2);
boolean bedrock = data.getToggle(3);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.getServers().add(new Server(address, port, online, bedrock));
// Send them back to the edit screen
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
/**
* Handle the server options response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerOptionsResponse(Player player, SimpleFormResponse data) {
// Take them back to the main menu if they close the edit server list window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
SimpleForm window = (SimpleForm) player.getCurrentWindow();
Server selectedServer = null;
for (Server server : player.getServers()) {
if (server.toString().equals(window.getContent())) {
selectedServer = server;
break;
}
}
if (selectedServer == null) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
switch (data.getClickedButtonId()) {
case 0:
player.sendWindow(FormID.EDIT_SERVER, getEditServer(player.getServers().indexOf(selectedServer), selectedServer));
break;
case 1:
player.sendWindow(FormID.REMOVE_SERVER, getRemoveServer(selectedServer));
break;
default:
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
break;
}
}
/**
* Handle the server remove response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleServerRemoveResponse(Player player, SimpleFormResponse data) {
SimpleForm window = (SimpleForm) player.getCurrentWindow();
String serverName = window.getContent().split(":")[1].trim();
Server selectedServer = null;
for (Server server : player.getServers()) {
if (server.toString().equals(serverName)) {
selectedServer = server;
break;
}
}
if (selectedServer == null) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
if (data.getClickedButtonId() == 0) {
player.getServers().remove(selectedServer);
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} else {
player.sendWindow(FormID.SERVER_OPTIONS, getServerOptions(selectedServer));
}
}
/**
* Handle the edit server response
*
* @param player The player that submitted the response
* @param data The form response data
*/
public static void handleEditServerResponse(Player player, CustomFormResponse data) {
// Take them back to the edit server list menu if they close the add server window
if (!data.isCorrect()) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
try {
int serverIndex = Integer.parseInt(((CustomForm)player.getCurrentWindow()).getContent().get(0).getText().split(":")[1].trim());
String address = data.getInput(1);
int port = Integer.parseInt(data.getInput(2));
boolean online = data.getToggle(3);
boolean bedrock = data.getToggle(4);
// Make sure we got an address
if (address == null || "".equals(address)) {
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
return;
}
// Make sure we got a valid port
if (port <= 0 || port >= 65535) {
player.resendWindow();
return;
}
player.getServers().set(serverIndex, new Server(address, port, online, bedrock));
// Send them back to the edit screen
player.sendWindow(FormID.EDIT_SERVERS, getEditServerList(player.getServers()));
} catch (NumberFormatException e) {
player.resendWindow();
}
}
}

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
import org.geysermc.connect.MasterServer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Function;
public final class GeyserConnectFileUtils {
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> format) throws IOException {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.createNewFile();
try (FileOutputStream fos = new FileOutputStream(file)) {
try (InputStream input = MasterServer.class.getClassLoader().getResourceAsStream(name)) {
byte[] bytes = new byte[input.available()];
//noinspection ResultOfMethodCallIgnored
input.read(bytes);
for(char c : format.apply(new String(bytes)).toCharArray()) {
fos.write(c);
}
fos.flush();
}
}
}
return file;
}
private GeyserConnectFileUtils() {
}
}

View file

@ -1,92 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
import lombok.extern.log4j.Log4j2;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.connect.MasterServer;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.text.ChatColor;
@Log4j2
public class Logger extends SimpleTerminalConsole implements GeyserLogger {
@Override
protected boolean isRunning() {
return !MasterServer.getInstance().isShuttingDown();
}
@Override
protected void runCommand(String line) {
// Dont do anything rn
}
@Override
protected void shutdown() {
MasterServer.getInstance().shutdown();
}
public void severe(String message) {
log.fatal(printConsole(ChatColor.DARK_RED + message));
}
public void severe(String message, Throwable error) {
log.fatal(printConsole(ChatColor.DARK_RED + message), error);
}
public void error(String message) {
log.error(printConsole(ChatColor.RED + message));
}
public void error(String message, Throwable error) {
log.error(printConsole(ChatColor.RED + message), error);
}
public void warning(String message) {
log.warn(printConsole(ChatColor.YELLOW + message));
}
public void info(String message) {
log.info(printConsole(ChatColor.WHITE + message));
}
public void debug(String message) {
log.debug(printConsole(ChatColor.GRAY + message));
}
public static String printConsole(String message) {
return ChatColor.toANSI(message + ChatColor.RESET);
}
public void setDebug(boolean debug) {
Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel());
}
public boolean isDebug() {
return log.isDebugEnabled();
}
}

View file

@ -1,318 +0,0 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/GeyserConnect
*/
package org.geysermc.connect.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.nukkitx.math.vector.Vector2f;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.packet.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connect.MasterServer;
import org.geysermc.connect.proxy.GeyserProxySession;
import org.geysermc.connect.ui.FormID;
import org.geysermc.cumulus.Form;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.network.UpstreamPacketHandler;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.auth.AuthData;
import org.geysermc.geyser.session.auth.BedrockClientData;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.DimensionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Getter
public class Player {
private static final byte[] EMPTY_CHUNK_DATA;
static {
ByteBuf byteBuf = Unpooled.buffer();
try {
for (int i = 0; i < 32; i++) {
byteBuf.writeBytes(ChunkUtils.EMPTY_BIOME_DATA);
}
byteBuf.writeByte(0); // Border
EMPTY_CHUNK_DATA = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(EMPTY_CHUNK_DATA);
} finally {
byteBuf.release();
}
}
private final AuthData authData;
@Setter
private JsonNode chainData;
private final BedrockServerSession session;
private final List<Server> servers = new ArrayList<>();
private final Long2ObjectMap<ModalFormRequestPacket> forms = new Long2ObjectOpenHashMap<>();
private Form currentWindow;
private FormID currentWindowId;
@Setter
private Server currentServer;
@Setter
private BedrockClientData clientData;
@Setter
private ServerCategory serverCategory;
public Player(AuthData authData, BedrockServerSession session) {
this.authData = authData;
this.session = session;
// Should fetch the servers from some form of db
if (MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().isEnabled()) {
servers.addAll(MasterServer.getInstance().getStorageManager().loadServers(this));
}
}
/**
* Send a few different packets to get the client to load in
*/
public void sendStartGame() {
ItemMappings itemMappings = Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion());
// A lot of this likely doesn't need to be changed
StartGamePacket startGamePacket = new StartGamePacket();
startGamePacket.setUniqueEntityId(1);
startGamePacket.setRuntimeEntityId(1);
startGamePacket.setPlayerGameType(GameType.CREATIVE);
startGamePacket.setPlayerPosition(Vector3f.from(0, 64 + 2, 0));
startGamePacket.setRotation(Vector2f.ONE);
startGamePacket.setSeed(-1L);
startGamePacket.setDimensionId(2);
startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGameType(GameType.CREATIVE);
startGamePacket.setDifficulty(0);
startGamePacket.setDefaultSpawn(Vector3i.ZERO);
startGamePacket.setAchievementsDisabled(true);
startGamePacket.setCurrentTick(-1);
startGamePacket.setEduEditionOffers(0);
startGamePacket.setEduFeaturesEnabled(false);
startGamePacket.setRainLevel(0);
startGamePacket.setLightningLevel(0);
startGamePacket.setMultiplayerGame(true);
startGamePacket.setBroadcastingToLan(true);
startGamePacket.getGamerules().add(new GameRuleData<>("showcoordinates", true));
startGamePacket.setPlatformBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setXblBroadcastMode(GamePublishSetting.PUBLIC);
startGamePacket.setCommandsEnabled(true);
startGamePacket.setTexturePacksRequired(false);
startGamePacket.setBonusChestEnabled(false);
startGamePacket.setStartingWithMap(false);
startGamePacket.setTrustingPlayers(true);
startGamePacket.setDefaultPlayerPermission(PlayerPermission.VISITOR);
startGamePacket.setServerChunkTickRange(4);
startGamePacket.setBehaviorPackLocked(false);
startGamePacket.setResourcePackLocked(false);
startGamePacket.setFromLockedWorldTemplate(false);
startGamePacket.setUsingMsaGamertagsOnly(false);
startGamePacket.setFromWorldTemplate(false);
startGamePacket.setWorldTemplateOptionLocked(false);
startGamePacket.setLevelId("");
startGamePacket.setLevelName("GeyserConnect");
startGamePacket.setPremiumWorldTemplateId("");
startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(itemMappings.getItemEntries());
startGamePacket.setInventoriesServerAuthoritative(true);
startGamePacket.setServerEngine("");
startGamePacket.setPlayerPropertyData(NbtMap.EMPTY);
startGamePacket.setWorldTemplateId(UUID.randomUUID());
startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE);
SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings();
settings.setMovementMode(AuthoritativeMovementMode.CLIENT);
settings.setRewindHistorySize(0);
settings.setServerAuthoritativeBlockBreaking(false);
startGamePacket.setPlayerMovementSettings(settings);
startGamePacket.setVanillaVersion("*");
session.sendPacket(startGamePacket);
if (!itemMappings.getComponentItemData().isEmpty()) {
ItemComponentPacket itemComponentPacket = new ItemComponentPacket();
itemComponentPacket.getItems().addAll(itemMappings.getComponentItemData());
session.sendPacket(itemComponentPacket);
}
// Send an empty chunk
LevelChunkPacket data = new LevelChunkPacket();
data.setChunkX(0);
data.setChunkZ(0);
data.setSubChunksLength(0);
data.setData(EMPTY_CHUNK_DATA);
data.setCachingEnabled(false);
session.sendPacket(data);
// Send the biomes
BiomeDefinitionListPacket biomeDefinitionListPacket = new BiomeDefinitionListPacket();
biomeDefinitionListPacket.setDefinitions(Registries.BIOMES_NBT.get());
session.sendPacket(biomeDefinitionListPacket);
AvailableEntityIdentifiersPacket entityPacket = new AvailableEntityIdentifiersPacket();
entityPacket.setIdentifiers(Registries.BEDROCK_ENTITY_IDENTIFIERS.get());
session.sendPacket(entityPacket);
// Send a CreativeContentPacket - required for 1.16.100
CreativeContentPacket creativeContentPacket = new CreativeContentPacket();
creativeContentPacket.setContents(itemMappings.getCreativeItems());
session.sendPacket(creativeContentPacket);
// Let the client know the player can spawn
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendPacket(playStatusPacket);
// Freeze the player
SetEntityMotionPacket setEntityMotionPacket = new SetEntityMotionPacket();
setEntityMotionPacket.setRuntimeEntityId(1);
setEntityMotionPacket.setMotion(Vector3f.ZERO);
session.sendPacket(setEntityMotionPacket);
}
/**
* Send a window with the specified id and content
* Also cache it against the player for later use
*
* @param id The {@link FormID} to use for the form
* @param window The {@link Form} to turn into json and send
*/
public void sendWindow(FormID id, Form window) {
this.currentWindow = window;
this.currentWindowId = id;
ModalFormRequestPacket modalFormRequestPacket = new ModalFormRequestPacket();
modalFormRequestPacket.setFormId(id.ordinal());
modalFormRequestPacket.setFormData(window.getJsonData());
session.sendPacket(modalFormRequestPacket);
// This packet is used to fix the image loading bug
NetworkStackLatencyPacket networkStackLatencyPacket = new NetworkStackLatencyPacket();
networkStackLatencyPacket.setFromServer(true);
networkStackLatencyPacket.setTimestamp(System.currentTimeMillis());
session.sendPacket(networkStackLatencyPacket);
}
public void resendWindow() {
sendWindow(currentWindowId, currentWindow);
}
/**
* Send the player to the Geyser proxy server or straight to the bedrock server if it is
*/
public void connectToProxy() {
if (currentServer.isBedrock()) {
TransferPacket transferPacket = new TransferPacket();
transferPacket.setAddress(currentServer.getAddress());
transferPacket.setPort(currentServer.getPort());
session.sendPacket(transferPacket);
} else {
GeyserProxySession geyserSession = createGeyserSession(true);
GeyserImpl geyser = geyserSession.getGeyser();
geyserSession.setDimension(DimensionUtils.THE_END);
geyserSession.remoteServer(currentServer);
// Tell Geyser to handle the login
SetLocalPlayerAsInitializedPacket initializedPacket = new SetLocalPlayerAsInitializedPacket();
initializedPacket.setRuntimeEntityId(geyserSession.getPlayerEntity().getGeyserId());
session.getPacketHandler().handle(initializedPacket);
if (geyser.getConfig().getSavedUserLogins().contains(authData.name())) {
String refreshToken = geyser.refreshTokenFor(authData.name());
if (refreshToken != null) {
geyserSession.authenticateWithRefreshToken(refreshToken);
}
}
if (geyserSession.remoteServer().authType() != AuthType.ONLINE) {
geyserSession.authenticate(geyserSession.getAuthData().name());
}
}
}
public GeyserProxySession createGeyserSession(boolean initialized) {
GeyserProxySession geyserSession = new GeyserProxySession(GeyserImpl.getInstance(), session,
MasterServer.getInstance().getEventLoopGroup().next(), this, initialized);
session.setPacketHandler(new UpstreamPacketHandler(GeyserImpl.getInstance(), geyserSession));
// The player will be tracked from Geyser from here
MasterServer.getInstance().getPlayers().remove(this);
GeyserImpl.getInstance().getSessionManager().addPendingSession(geyserSession);
geyserSession.getUpstream().getSession().setPacketCodec(session.getPacketCodec());
// Set the block translation based off of version
geyserSession.setBlockMappings(BlockRegistries.BLOCKS.forVersion(session.getPacketCodec().getProtocolVersion()));
geyserSession.setItemMappings(Registries.ITEMS.forVersion(session.getPacketCodec().getProtocolVersion()));
geyserSession.setAuthData(authData);
geyserSession.setCertChainData(chainData);
geyserSession.setClientData(clientData);
return geyserSession;
}
public void sendToServer(Server server) {
// Geyser will show a "please wait" message in action bar
// Send the user over to the server
setCurrentServer(server);
connectToProxy();
}
public List<Server> getCurrentServers() {
if (serverCategory == ServerCategory.CUSTOM) {
return servers;
}
return MasterServer.getInstance().getServers(serverCategory);
}
}

View file

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

View file

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

View file

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

View file

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