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 name: Build Pull Request
on: [pull_request] on:
pull_request:
paths-ignore:
- '.idea/copyright/*.xml'
- '.gitignore'
- 'LICENSE'
- 'README.md'
- 'bind9/**'
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - name: Set up JDK 17
- 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
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 16 java-version: 17
- name: Build with Maven distribution: temurin
run: mvn -B package
- name: Checkout repository
uses: actions/checkout@v1
- name: Build Geyser
uses: gradle/gradle-build-action@v2
with:
arguments: build
- name: Archive artifacts - name: Archive artifacts
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v1
if: success() if: success()
with: with:
name: Build 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 # Created by https://www.toptal.com/developers/gitignore/api/git,java,gradle,eclipse,netbeans,jetbrains+all
# Edit at https://www.gitignore.io/?templates=git,java,maven,eclipse,netbeans,jetbrains+all # Edit at https://www.toptal.com/developers/gitignore?templates=git,java,gradle,eclipse,netbeans,jetbrains+all
### Eclipse ### ### Eclipse ###
.metadata .metadata
@ -53,22 +53,19 @@ local.properties
# Annotation Processing # Annotation Processing
.apt_generated/ .apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse) # Scala IDE specific (Scala & Java development for Eclipse)
.cache-main .cache-main
.scala_dependencies .scala_dependencies
.worksheet .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 Patch ###
# Eclipse Core # Spring Boot Tooling
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
.sts4-cache/ .sts4-cache/
### Git ### ### Git ###
@ -110,9 +107,10 @@ local.properties
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
replay_pid*
### JetBrains+all ### ### 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 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
@ -122,6 +120,9 @@ hs_err_pid*
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files # Generated files
.idea/**/contentModel.xml .idea/**/contentModel.xml
@ -142,6 +143,9 @@ hs_err_pid*
# When using Gradle or Maven with auto-import, you should exclude module files, # 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 # since they will be recreated, and may cause churn. Uncomment if using
# auto-import. # auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml # .idea/modules.xml
# .idea/*.iml # .idea/*.iml
# .idea/modules # .idea/modules
@ -169,6 +173,9 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin # Cursive Clojure plugin
.idea/replstate.xml .idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ) # Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml com_crashlytics_export_strings.xml
crashlytics.properties crashlytics.properties
@ -182,32 +189,13 @@ fabric.properties
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### JetBrains+all Patch ### ### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files # Ignore everything but code style settings and run configurations
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 # that are supposed to be shared within teams.
#.idea/ .idea/*
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 !.idea/codeStyles
!.idea/runConfigurations
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
### NetBeans ### ### NetBeans ###
**/nbproject/private/ **/nbproject/private/
@ -219,15 +207,35 @@ dist/
nbdist/ nbdist/
.nb-gradle/ .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 # Copyright header files
.idea/* .idea/*
!.idea/copyright/ !.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/) [![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) [![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/) [![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) [![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. GeyserConnect is an easy way for Bedrock Edition clients to connect to any Java Edition servers without having to run anything.
## What is GeyserConnect? ## 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 use DNS redirection please see the [bind9](bind9) folder in this repository.
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)

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 * @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) { import java.util.List;
new MasterServer();
} 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 * @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.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.geysermc.connect.MasterServer; import org.geysermc.connect.extension.GeyserConnect;
import org.geysermc.connect.utils.Player; import org.geysermc.connect.extension.utils.Server;
import org.geysermc.connect.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.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.ArrayList;
import java.util.List; import java.util.List;
public abstract class AbstractSQLStorageManager extends AbstractStorageManager { public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
private final ObjectMapper mapper = new ObjectMapper();
protected Connection connection; protected Connection connection;
@Override @Override
@ -49,8 +53,19 @@ public abstract class AbstractSQLStorageManager extends AbstractStorageManager {
try (Statement createPlayersTable = connection.createStatement()) { try (Statement createPlayersTable = connection.createStatement()) {
createPlayersTable.executeUpdate("CREATE TABLE IF NOT EXISTS players (xuid VARCHAR(32), servers TEXT, PRIMARY KEY(xuid));"); 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) { } 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 { try {
connection.close(); connection.close();
} catch (SQLException exception) { } catch (SQLException exception) {
MasterServer.getInstance().getLogger().error("Failed to close SQL connection", exception); GeyserConnect.instance().logger().error("Failed to close SQL connection", exception);
} }
} }
@Override @Override
public void saveServers(Player player) { public void saveServers(GeyserSession session) {
// replace into works on MySQL and SQLite // replace into works on MySQL and SQLite
try (PreparedStatement updatePlayersServers = connection.prepareStatement("REPLACE INTO players(xuid, servers) VALUES(?, ?)")) { try (PreparedStatement updatePlayersServers = connection.prepareStatement("REPLACE INTO players(xuid, servers) VALUES(?, ?)")) {
updatePlayersServers.setString(1, player.getAuthData().xuid()); updatePlayersServers.setString(1, session.getAuthData().xuid());
updatePlayersServers.setString(2, mapper.writeValueAsString(player.getServers())); updatePlayersServers.setString(2, Utils.OBJECT_MAPPER.writeValueAsString(ServerManager.getServers(session)));
updatePlayersServers.executeUpdate(); updatePlayersServers.executeUpdate();
} catch (IOException | SQLException exception) { } 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 @Override
public List<Server> loadServers(Player player) { public List<Server> loadServers(GeyserSession session) {
List<Server> servers = new ArrayList<>(); List<Server> servers = new ArrayList<>();
try (PreparedStatement getPlayersServers = connection.prepareStatement("SELECT servers FROM players WHERE xuid=?")) { 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(); ResultSet rs = getPlayersServers.executeQuery();
while (rs.next()) { 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); servers.addAll(loadedServers);
}
} }
} catch (IOException | SQLException exception) { } 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; return servers;

View file

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

View file

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

View file

@ -23,18 +23,19 @@
* @link https://github.com/GeyserMC/GeyserConnect * @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.extension.GeyserConnect;
import org.geysermc.connect.MasterServer; import org.geysermc.connect.extension.config.MySQLConnectionSection;
import java.sql.*; import java.sql.DriverManager;
import java.sql.SQLException;
public class MySQLStorageManager extends AbstractSQLStorageManager { public class MySQLStorageManager extends AbstractSQLStorageManager {
@Override @Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException { protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver"); Class.forName("com.mysql.cj.jdbc.Driver");
GeyserConnectConfig.MySQLConnectionSection connectionInformation = MasterServer.getInstance().getGeyserConnectConfig().getCustomServers().getMysql(); MySQLConnectionSection connectionInformation = GeyserConnect.instance().config().customServers().mysql();
connection = DriverManager.getConnection("jdbc:mysql://" + connectionInformation.getHost() + ":" + connectionInformation.getPort() + "/" + connectionInformation.getDatabase(), connectionInformation.getUser(), connectionInformation.getPass()); 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 * @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 { public class SQLiteStorageManager extends AbstractSQLStorageManager {
@Override @Override
protected void connectToDatabase() throws ClassNotFoundException, SQLException { protected void connectToDatabase() throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC"); 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 * @link https://github.com/GeyserMC/GeyserConnect
*/ */
package org.geysermc.connect.utils; package org.geysermc.connect.extension.utils;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.cumulus.util.FormImage; import org.geysermc.cumulus.util.FormImage;
import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.api.network.RemoteServer;
@Getter public record Server(
@AllArgsConstructor String address,
@NoArgsConstructor int port,
public class Server implements RemoteServer { boolean online,
boolean bedrock,
private String address; String name,
private int port = -1; String imageUrl,
private boolean online = true; ServerCategory category
private boolean bedrock = false; ) implements RemoteServer {
private String name = null;
private String imageUrl = null;
private ServerCategory category = null;
public Server(String address) {
this(address, -1);
}
public Server(String address, int port) { public Server(String address, int port) {
this(address, port, true); this(address, port, true, false, null, null, ServerCategory.CUSTOM);
}
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);
} }
private int defaultPort() { return bedrock ? 19132 : 25565; } 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 @Override
public int port() { public int port() {
return port; return port < 0 ? defaultPort() : port;
} }
@Override @Override
@ -104,6 +56,15 @@ public class Server implements RemoteServer {
return this.online ? AuthType.ONLINE : AuthType.OFFLINE; 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 @Override
public String minecraftVersion() { public String minecraftVersion() {
return null; return null;
@ -113,4 +74,8 @@ public class Server implements RemoteServer {
public int protocolVersion() { public int protocolVersion() {
return 0; 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 * @link https://github.com/GeyserMC/GeyserConnect
*/ */
package org.geysermc.connect.utils; package org.geysermc.connect.extension.utils;
import lombok.Getter;
@Getter
public enum ServerCategory { public enum ServerCategory {
OFFICIAL("Official"), OFFICIAL("Official"),
GEYSER("Geyser"), GEYSER("Geyser"),
@ -38,4 +35,8 @@ public enum ServerCategory {
ServerCategory(String title) { ServerCategory(String title) {
this.title = 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 # 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 # 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 # This is loaded live so will update without a server restart
welcome-file: welcome.txt 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 # A global list of servers sent to all clients
servers: servers:
- name: The Hive - name: The Hive
@ -42,49 +13,49 @@ servers:
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://i.imgur.com/VQJW6XG.png' imageUrl: 'https://i.imgur.com/myXmr7B.png'
- name: CubeCraft - name: CubeCraft
address: 94.23.159.81 address: 94.23.159.81
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://www.cubecraft.net/attachments/q3pmrsod_400x400-png.53219/' imageUrl: 'https://i.imgur.com/f83ZeDS.jpg'
- name: Galaxite - name: Galaxite
address: 54.39.243.199 address: 54.39.243.199
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://pbs.twimg.com/profile_images/1275867042583896066/UMPF5nTM_400x400.jpg' imageUrl: 'https://i.imgur.com/PEkLROT.jpg'
- name: Lifeboat Network - name: Lifeboat Network
address: 63.143.54.198 address: 63.143.54.198
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://lbsg.net/wp-content/uploads/2017/06/lifeboat-square.png' imageUrl: 'https://i.imgur.com/GjsxhCI.png'
- name: Mineplex - name: Mineplex
address: 108.178.12.38 address: 108.178.12.38
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://www.mineplex.com/assets/www-mp/img/footer/footer_smalllogo.png' imageUrl: 'https://i.imgur.com/xB6yncS.png'
- name: MineVille - name: MineVille
address: 52.234.130.255 address: 52.234.130.255
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL category: OFFICIAL
imageUrl: 'https://pbs.twimg.com/profile_images/1332400307050045441/MHQvGEUP_400x400.jpg' imageUrl: 'https://i.imgur.com/yqrfMhP.jpg'
- name: Pixel Paradise - name: Pixel Paradise
address: 40.87.84.59 address: 40.87.84.59
port: 19132 port: 19132
bedrock: true bedrock: true
category: OFFICIAL 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 - name: Official Geyser Test Server
address: test.geysermc.org 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