1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Merge pull request #397 from GeyserMC/development

* Started using plugin messages internally. Started subcommand refactor

* Removed config holder and changed a few things

* Small changes to platforms and injectors

* Changed how post-enable messages work internally + minor other changes

* Deprecate handshake handlers and player properties

* Added auto-binding

* Switched event library

* Fixed a circular dependency issue when a locale couldn't be found

* Simplified plugin message channel logic

* Switched to Hikari for MySQL

* SkinApplier now only applies a skin if a player doesn't already have one (#330)

* SkinApplier now only applies a skin if a player doesn't already have one

* add `hasSkin` method to SkinApplier and check for exising skins before overwriting

* remove the use of Streams and Optionals

* correct delay in SpigotSkinApplier to use ticks instead of milliseconds

* Minor changes

Co-authored-by: Tim203 <mctim203@gmail.com>

* Updated to the latest events version. Share a thread pool

* News needs a scheduled executor

* Bumped version to 2.1.1 and publish entire java component

* Fix checking for existing skins on Spigot (#362)

* Close all skin sockets on shutdown (#363)

* Only apply skin when someone doesn't have a skin applied already (#365)

Instead of aplying only when the player has a skin apply only when it doesn't have a skin

* Use UTF-8 for language files (#366)

Languages like ru_RU don't work because they have specific characters, and your files are encoded in UTF-8, but it reads them as ISO 8859-1

* Fixed building Floodgate and added a version subcommand

* Remove Blossom and use templates

* Use weak references for injected Netty channels

* Use newSetFromMap

* Added a SkinApplyEvent that can cancel/edit the to be applied skin

* Use a common ScheduledThreadPool and make the player map concurrent

* Don't try to remove all injector references

* Add branch name when not master, simplify publish, use GitHub Actions

And updated Gradle

* Remove Jenkinsfile

* Retrieve version from gradle.properties

* Let's see if the setup-java action's cache is helpful & fixed building

* Add publishing to downloads site (#385)

* Add publishing to downloads site

* Only publish to Downloads API when branch is master and status success

---------

Co-authored-by: Tim203 <mctim203@gmail.com>

* Notify Discord after building & fixed building

* Temporarily restore Jenkinsfile to give people time to switch

* Localize floodgate.core.not_linked (#383)

* Localize floodgate.core.not_linked

* Update languages submodule

* not_linked string has changed slightly

---------

Co-authored-by: Tim203 <mctim203@gmail.com>

* Update bstats dependency

* Only publish and notify Discord on push if running inside this repository (#387)

* Only attempt publishing if running in GeyserMC/Floodgate

* Also restrict the discord notification

* Fix injection of LanguageManager/Slf4jFloodgate on Velocity (circular proxy) (#388)

* Made various changes to the build action

* Update maven deploy location

* Relocate MySQL database extension dependencies

* Shutdown metrics on platform shutdown (#386)

* Shutdown metrics on platorm shutdown

* Listen to event instead of hardcoding it

* Annotate Metrics as a Listener

* Use temporary bStats fork to properly shutdown bStats

* Use bstats-base dependency (instead of the whole project I guess?)

* Formatting change

---------

Co-authored-by: Tim203 <mctim203@gmail.com>

---------

Co-authored-by: Alex <me@teamplayer.io>
Co-authored-by: Konicai <71294714+Konicai@users.noreply.github.com>
Co-authored-by: mastermc05 <63639746+mastermc05@users.noreply.github.com>
Co-authored-by: rtm516 <rtm516@users.noreply.github.com>
This commit is contained in:
Tim203
2023-03-01 13:37:05 +01:00
committed by GitHub
103 changed files with 2485 additions and 1385 deletions

63
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-java@v3
with:
java-version: 8
distribution: temurin
- name: Build
uses: gradle/gradle-build-action@v2
with:
arguments: build
cache-read-only: ${{ github.ref_name != 'master' && github.ref_name != 'development' }}
- name: Publish to Maven Repository
if: ${{ github.repository == 'GeyserMC/Floodgate' }}
uses: gradle/gradle-build-action@v2
env:
ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }}
ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }}
with:
arguments: publish
cache-read-only: ${{ github.ref_name != 'master' && github.ref_name != 'development' }}
- name: Publish to Downloads API
if: ${{ github.ref_name == 'master' && github.repository == 'GeyserMC/Floodgate' }}
shell: bash
env:
DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }}
DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }}
DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }}
run: |
# Save the private key to a file
echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa
chmod 600 id_ecdsa
# Get the version from gradle.properties
version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2)
# Copy over artifacts
scp -B -o StrictHostKeyChecking=no -i id_ecdsa **/build/libs/floodgate-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/files/
# Remove un-needed artifacts
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP rm ~/files/floodgate-parent-*.jar ~/files/floodgate-api.jar ~/files/floodgate-core.jar
# Run the build script
ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP ./handleBuild.sh floodgate $version $GITHUB_RUN_NUMBER $GITHUB_SHA
- name: Notify Discord
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Floodgate' }}
uses: Tim203/actions-git-discord-webhook@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

97
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# Created by https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
# Edit at https://www.gitignore.io/?templates=git,java,maven,eclipse,netbeans,jetbrains+all
# Created by https://www.gitignore.io/api/git,java,gradle,eclipse,netbeans,jetbrains+all
# Edit at https://www.gitignore.io/?templates=git,gradle,maven,eclipse,netbeans,jetbrains+all
### Eclipse ###
.metadata
@@ -52,22 +52,19 @@ local.properties
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
### Eclipse Patch ###
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
# Spring Boot Tooling
.sts4-cache/
### Git ###
@@ -109,9 +106,10 @@ local.properties
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
@@ -121,6 +119,9 @@ hs_err_pid*
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
@@ -141,11 +142,14 @@ hs_err_pid*
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
@@ -168,6 +172,9 @@ atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
@@ -181,32 +188,13 @@ fabric.properties
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/
.idea/*
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
!.idea/codeStyles
!.idea/runConfigurations
### NetBeans ###
**/nbproject/private/
@@ -218,8 +206,29 @@ dist/
nbdist/
.nb-gradle/
# End of https://www.gitignore.io/api/git,java,maven,eclipse,netbeans,jetbrains+all
gradle/
**/.gradle/
### 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
# End of https://www.gitignore.io/api/git,java,gradle,eclipse,netbeans,jetbrains+all
/core/src/main/resources/languages/

82
Jenkinsfile vendored
View File

@@ -26,87 +26,5 @@ pipeline {
}
}
}
stage ('Deploy') {
when {
anyOf {
branch "master"
branch "development"
}
}
steps {
rtGradleDeployer(
id: "GRADLE_DEPLOYER",
serverId: "opencollab-artifactory",
releaseRepo: "maven-releases",
snapshotRepo: "maven-snapshots"
)
rtGradleResolver(
id: "GRADLE_RESOLVER",
serverId: "opencollab-artifactory"
)
rtGradleRun(
usesPlugin: true,
tool: 'Gradle 7',
rootDir: "",
useWrapper: true,
buildFile: 'build.gradle.kts',
tasks: 'artifactoryPublish',
deployerId: "GRADLE_DEPLOYER",
resolverId: "GRADLE_RESOLVER"
)
rtPublishBuildInfo(
serverId: "opencollab-artifactory"
)
}
}
}
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()
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.opencollab.dev/job/GeyserMC/job/Floodgate)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
success {
script {
if (env.BRANCH_NAME == 'master') {
build propagate: false, wait: false, job: 'GeyserMC/Floodgate-Fabric/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
}
}
}
}
}

0
ap/build.gradle.kts Normal file
View File

View File

@@ -23,37 +23,16 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.config;
package org.geysermc.floodgate.ap;
import org.geysermc.floodgate.util.FloodgateInfoHolder;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
public class FloodgateConfigHolder {
private FloodgateConfig config;
public boolean has() {
return config != null;
}
public boolean isProxy() {
return config instanceof ProxyFloodgateConfig;
}
public FloodgateConfig get() {
return config;
}
public ProxyFloodgateConfig getAsProxy() {
return getAs();
}
@SuppressWarnings("unchecked")
public <T extends FloodgateConfig> T getAs() {
return (T) config;
}
public void set(FloodgateConfig config) {
this.config = config;
// for Geyser dump
FloodgateInfoHolder.setConfig(config);
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoBindProcessor extends ClassProcessor {
public AutoBindProcessor() {
super("org.geysermc.floodgate.util.AutoBind");
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.ap;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/*
* Copied from Geyser
*/
public class ClassProcessor extends AbstractProcessor {
private final String annotationClassName;
private Path outputPath;
private final Set<String> locations = new HashSet<>();
public ClassProcessor(String annotationClassName) {
this.annotationClassName = annotationClassName;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
processingEnv.getMessager().printMessage(
Kind.NOTE, "Initializing processor " + annotationClassName
);
String outputFile = processingEnv.getOptions().get("metadataOutputFile");
if (outputFile != null && !outputFile.isEmpty()) {
outputPath = Paths.get(outputFile);
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
if (!roundEnv.errorRaised()) {
complete();
}
return false;
}
if (!contains(annotations, annotationClassName)) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() != ElementKind.CLASS) {
continue;
}
if (!contains(element.getAnnotationMirrors(), annotationClassName)) {
continue;
}
TypeElement typeElement = (TypeElement) element;
locations.add(typeElement.getQualifiedName().toString());
}
return false;
}
public boolean contains(Collection<? extends TypeElement> elements, String className) {
if (elements.isEmpty()) {
return false;
}
for (TypeElement element : elements) {
if (element.getQualifiedName().contentEquals(className)) {
return true;
}
}
return false;
}
public boolean contains(List<? extends AnnotationMirror> elements, String className) {
if (elements.isEmpty()) {
return false;
}
for (AnnotationMirror element : elements) {
if (element.getAnnotationType().toString().equals(className)) {
return true;
}
}
return false;
}
public void complete() {
// Read existing annotation list and verify each class still has this annotation
try (BufferedReader reader = createReader()) {
if (reader != null) {
reader.lines().forEach(canonicalName -> {
if (!locations.contains(canonicalName)) {
TypeElement element =
processingEnv.getElementUtils().getTypeElement(canonicalName);
if (element != null && element.getKind() == ElementKind.CLASS &&
contains(element.getAnnotationMirrors(), annotationClassName)) {
locations.add(canonicalName);
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
if (!locations.isEmpty()) {
try (BufferedWriter writer = createWriter()) {
for (String location : locations) {
writer.write(location);
writer.newLine();
}
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
processingEnv.getMessager().printMessage(Kind.NOTE,
"Did not find any classes annotated with " + annotationClassName
);
}
processingEnv.getMessager().printMessage(
Kind.NOTE, "Completed processing for " + annotationClassName
);
}
private BufferedReader createReader() throws IOException {
if (outputPath != null) {
processingEnv.getMessager().printMessage(Kind.NOTE,
"Reading existing " + annotationClassName + " list from " + outputPath
);
return Files.newBufferedReader(outputPath);
}
FileObject obj = processingEnv.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", annotationClassName
);
if (obj != null) {
processingEnv.getMessager().printMessage(
Kind.NOTE,
"Reading existing " + annotationClassName + " list from " + obj.toUri()
);
try {
return new BufferedReader(obj.openReader(false));
} catch (NoSuchFileException ignored) {}
}
return null;
}
private BufferedWriter createWriter() throws IOException {
if (outputPath != null) {
processingEnv.getMessager().printMessage(
Kind.NOTE, "Writing " + annotationClassName + " to " + outputPath
);
return Files.newBufferedWriter(outputPath);
}
FileObject obj = processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "", annotationClassName
);
processingEnv.getMessager().printMessage(
Kind.NOTE, "Writing " + annotationClassName + " to " + obj.toUri()
);
return new BufferedWriter(obj.openWriter());
}
}

View File

@@ -0,0 +1 @@
org.geysermc.floodgate.ap.AutoBindProcessor

View File

@@ -1,6 +1,7 @@
dependencies {
api("org.geysermc", "common", Versions.geyserVersion)
api("org.geysermc.cumulus", "cumulus", Versions.cumulusVersion)
api("org.geysermc.event", "events", Versions.eventsVersion)
compileOnly("io.netty", "netty-transport", Versions.nettyVersion)
}

View File

@@ -30,6 +30,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.unsafe.Unsafe;
@@ -148,6 +149,10 @@ public interface FloodgateApi {
*/
CompletableFuture<String> getGamertagFor(long xuid);
default FloodgateEventBus getEventBus() {
return InstanceHolder.getEventBus();
}
/**
* Returns the instance that manages all the linking.
*/

View File

@@ -27,6 +27,7 @@ package org.geysermc.floodgate.api;
import java.util.UUID;
import lombok.Getter;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
@@ -35,6 +36,7 @@ import org.geysermc.floodgate.api.packet.PacketHandlers;
public final class InstanceHolder {
@Getter private static FloodgateApi api;
@Getter private static PlayerLink playerLink;
@Getter private static FloodgateEventBus eventBus;
@Getter private static PlatformInjector injector;
@Getter private static PacketHandlers packetHandlers;
@@ -44,11 +46,12 @@ public final class InstanceHolder {
public static boolean set(
FloodgateApi floodgateApi,
PlayerLink link,
FloodgateEventBus floodgateEventBus,
PlatformInjector platformInjector,
PacketHandlers packetHandlers,
HandshakeHandlers handshakeHandlers,
UUID key) {
UUID key
) {
if (storedKey != null) {
if (!storedKey.equals(key)) {
return false;
@@ -59,14 +62,10 @@ public final class InstanceHolder {
api = floodgateApi;
playerLink = link;
eventBus = floodgateEventBus;
injector = platformInjector;
InstanceHolder.packetHandlers = packetHandlers;
InstanceHolder.handshakeHandlers = handshakeHandlers;
return true;
}
@SuppressWarnings("unchecked")
public static <T extends FloodgateApi> T castApi(Class<T> cast) {
return (T) api;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.api.event;
import org.geysermc.event.bus.EventBus;
public interface FloodgateEventBus extends EventBus<Object, FloodgateSubscriber<?>> {
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.api.event;
import org.geysermc.event.subscribe.Subscriber;
public interface FloodgateSubscriber<T> extends Subscriber<T> {
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.api.event.skin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.returnsreceiver.qual.This;
import org.geysermc.event.Cancellable;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
/**
* An event that's fired when Floodgate receives a player skin. The event will be cancelled by
* default when hasSkin is true, as Floodgate by default only applies skins when the player has no
* skin applied yet.
*/
public interface SkinApplyEvent extends Cancellable {
/**
* Returns the player that will receive the skin.
*/
@NonNull FloodgatePlayer player();
/**
* Returns the skin texture currently applied to the player.
*/
@Nullable SkinData currentSkin();
/**
* Returns the skin texture to be applied to the player.
*/
@NonNull SkinData newSkin();
/**
* Sets the skin texture to be applied to the player
*
* @param skinData the skin to apply
* @return this
*/
@This SkinApplyEvent newSkin(@NonNull SkinData skinData);
interface SkinData {
/**
* Returns the value of the skin texture.
*/
@NonNull String value();
/**
* Returns the signature of the skin texture.
*/
@NonNull String signature();
}
}

View File

@@ -37,6 +37,7 @@ import org.geysermc.floodgate.util.LinkedPlayer;
* server. Note that at the time I'm writing this that the HandshakeData is created after requesting
* the player link. So the link is present here, if applicable.
*/
@Deprecated
public interface HandshakeData {
/**
* Returns the Channel holding the connection between the client and the server.

View File

@@ -34,6 +34,7 @@ package org.geysermc.floodgate.api.handshake;
* HandshakeData#isFloodgatePlayer()} will be false and Floodgate related methods will return null
* for Java players
*/
@Deprecated
@FunctionalInterface
public interface HandshakeHandler {
/**

View File

@@ -25,6 +25,13 @@
package org.geysermc.floodgate.api.handshake;
/**
* @deprecated This system has been deprecated and will not be available in the new API that will be
* introduced when Geyser will include Floodgate (and thus will have some common base API).
* <br>
* It might be replaced with an event (probably internal), but that isn't certain yet.
*/
@Deprecated
public interface HandshakeHandlers {
/**
* Register a custom handshake handler. This can be used to check and edit the player during the

View File

@@ -37,10 +37,9 @@ public interface PlatformInjector {
* Injects the server connection. This will allow various addons (like getting the Floodgate
* data and debug mode) to work.
*
* @return true if the connection has successfully been injected
* @throws Exception if something went wrong while injecting the server connection
* @throws Exception if the platform couldn't be injected
*/
boolean inject() throws Exception;
void inject() throws Exception;
/**
* Some platforms may not be able to remove their injection process. If so, this method will
@@ -56,10 +55,9 @@ public interface PlatformInjector {
* Removes the injection from the server. Please note that this function should only be used
* internally (on plugin shutdown). This method will also remove every added addon.
*
* @return true if the injection has successfully been removed
* @throws Exception if something went wrong while removing the injection
* @throws Exception if the platform injection could not be removed
*/
boolean removeInjection() throws Exception;
void removeInjection() throws Exception;
/**
* If the server connection is currently injected.

View File

@@ -34,10 +34,6 @@ public enum LinkRequestResult {
* An unknown error encountered while creating / verifying the link request.
*/
UNKNOWN_ERROR,
/**
* @deprecated this result isn't used. Instead the link code is returned
*/
REQUEST_CREATED,
/**
* The specified bedrock username is already linked to a Java account.
*/

View File

@@ -80,18 +80,7 @@ public interface FloodgateLogger {
void trace(String message, Object... args);
/**
* Enables debug mode for the Floodgate logger.
*/
void enableDebug();
/**
* Disables debug mode for the Floodgate logger. Debug messages can still be sent after running
* this method, but they will be hidden from the console.
*/
void disableDebug();
/**
* Returns if debugging is enabled
* Returns true if debugging is enabled
*/
boolean isDebug();
}

View File

@@ -143,20 +143,28 @@ public interface FloodgatePlayer {
return FloodgateApi.getInstance().transferPlayer(getCorrectUniqueId(), address, port);
}
@Deprecated
boolean hasProperty(PropertyKey key);
@Deprecated
boolean hasProperty(String key);
@Deprecated
<T> T getProperty(PropertyKey key);
@Deprecated
<T> T getProperty(String key);
@Deprecated
<T> T removeProperty(PropertyKey key);
@Deprecated
<T> T removeProperty(String key);
@Deprecated
<T> T addProperty(PropertyKey key, Object value);
@Deprecated
<T> T addProperty(String key, Object value);
/**

View File

@@ -28,6 +28,7 @@ package org.geysermc.floodgate.api.player;
import lombok.Getter;
@Getter
@Deprecated
public class PropertyKey {
/**
* Socket Address returns the InetSocketAddress of the Bedrock player

View File

@@ -9,9 +9,10 @@ repositories {
}
dependencies {
implementation("net.kyori", "indra-common", "2.0.6")
implementation("org.jfrog.buildinfo", "build-info-extractor-gradle", "4.26.1")
implementation("net.kyori", "indra-common", "3.0.1")
implementation("net.kyori", "indra-git", "3.0.1")
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.1")
implementation("gradle.plugin.org.jetbrains.gradle.plugin.idea-ext", "gradle-idea-ext", "1.1.7")
}
tasks.withType<KotlinCompile> {

View File

@@ -26,14 +26,15 @@
object Versions {
const val geyserVersion = "2.0.7-SNAPSHOT"
const val cumulusVersion = "1.1.1"
const val eventsVersion = "1.0-SNAPSHOT"
const val configUtilsVersion = "1.0-SNAPSHOT"
const val spigotVersion = "1.13-R0.1-SNAPSHOT"
const val fastutilVersion = "8.5.3"
const val guiceVersion = "5.0.1"
const val guiceVersion = "5.1.0"
const val nettyVersion = "4.1.49.Final"
const val snakeyamlVersion = "1.28"
const val cloudVersion = "1.5.0"
const val bstatsVersion = "3.0.0"
const val bstatsVersion = "d2fbbd6823"
const val javaWebsocketVersion = "1.5.2"

View File

@@ -28,9 +28,6 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.kotlin.dsl.the
fun Project.isSnapshot(): Boolean =
version.toString().endsWith("-SNAPSHOT")
fun Project.fullVersion(): String {
var version = version.toString()
if (version.endsWith("-SNAPSHOT")) {
@@ -42,14 +39,19 @@ fun Project.fullVersion(): String {
fun Project.lastCommitHash(): String? =
the<IndraGitExtension>().commit()?.name?.substring(0, 7)
// retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
// some properties might be specific to Jenkins
fun Project.branchName(): String =
System.getenv("GIT_BRANCH") ?: "local/dev"
fun Project.buildNumber(): Int =
Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1")
the<IndraGitExtension>().branchName() ?: System.getenv("BRANCH_NAME") ?: "local/dev"
fun Project.buildNumberAsString(): String =
fun Project.shouldAddBranchName(): Boolean =
System.getenv("IGNORE_BRANCH")?.toBoolean() ?: (branchName() !in arrayOf("master", "local/dev"))
fun Project.versionWithBranchName(): String =
branchName().replace(Regex("[^0-9A-Za-z-_]"), "-") + '-' + version
fun buildNumber(): Int =
System.getenv("BUILD_NUMBER")?.let { Integer.parseInt(it) } ?: -1
fun buildNumberAsString(): String =
buildNumber().takeIf { it != -1 }?.toString() ?: "??"
val providedDependencies = mutableMapOf<String, MutableSet<Pair<String, Any>>>()

View File

@@ -1,7 +1,7 @@
plugins {
`java-library`
`maven-publish`
// id("net.ltgt.errorprone")
id("net.kyori.indra")
id("net.kyori.indra.git")
}
@@ -9,6 +9,21 @@ dependencies {
compileOnly("org.checkerframework", "checker-qual", Versions.checkerQual)
}
indra {
github("GeyserMC", "Floodgate") {
ci(true)
issues(true)
scm(true)
}
mitLicense()
javaVersions {
// without toolchain & strictVersion sun.misc.Unsafe won't be found
minimumToolchain(8)
strictVersions(true)
}
}
tasks {
processResources {
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) {
@@ -22,14 +37,4 @@ tasks {
)
}
}
compileJava {
options.encoding = Charsets.UTF_8.name()
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
}

View File

@@ -0,0 +1,93 @@
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.plugins.ide.eclipse.model.EclipseModel
import org.gradle.plugins.ide.idea.model.IdeaModel
import org.jetbrains.gradle.ext.ProjectSettings
import org.jetbrains.gradle.ext.TaskTriggersConfig
plugins {
id("org.jetbrains.gradle.plugin.idea-ext")
}
registerGenerateTemplateTasks()
fun Project.registerGenerateTemplateTasks() {
// main and test
extensions.getByType<SourceSetContainer>().all {
val javaDestination = layout.buildDirectory.dir("generated/sources/templates/$name")
val javaSrcDir = layout.projectDirectory.dir("src/$name/templates")
val javaGenerateTask = tasks.register<GenerateSourceTemplates>(
getTaskName("template", "sources")
) {
filteringCharset = Charsets.UTF_8.name()
from(javaSrcDir)
into(javaDestination)
filter<ReplaceTokens>("tokens" to replacements())
}
java.srcDir(javaGenerateTask.map { it.outputs })
val resourcesDestination = layout.buildDirectory.dir("generated/resources/templates/$name")
val resourcesSrcDir = layout.projectDirectory.dir("src/$name/resourceTemplates")
val resourcesGenerateTask = tasks.register<GenerateResourceTemplates>(
getTaskName("template", "resources")
) {
filteringCharset = Charsets.UTF_8.name()
from(resourcesSrcDir)
into(resourcesDestination)
filter<ReplaceTokens>("tokens" to replacements())
}
resources.srcDir(resourcesGenerateTask.map { it.outputs })
}
return configureIdeSync(
tasks.register("allTemplateSources") {
dependsOn(tasks.withType<GenerateSourceTemplates>())
},
tasks.register("allTemplateResources") {
dependsOn(tasks.withType<GenerateResourceTemplates>())
}
)
}
fun Project.configureIdeSync(vararg generateAllTasks: TaskProvider<Task>) {
extensions.findByType<EclipseModel> {
synchronizationTasks(generateAllTasks)
}
extensions.findByType<IdeaModel> {
if (project != null) {
(project as ExtensionAware).extensions.configure<ProjectSettings> {
(this as ExtensionAware).extensions.configure<TaskTriggersConfig> {
afterSync(generateAllTasks)
}
}
}
}
//todo wasn't able to find something for VS(Code)
}
inline fun <reified T : Any> ExtensionContainer.findByType(noinline action: T.() -> Unit) {
val extension = findByType(T::class)
if (extension != null) {
action.invoke(extension)
}
}
abstract class GenerateAnyTemplates : Copy() {
private val replacements = mutableMapOf<String, String>()
fun replaceToken(key: String, value: () -> Any) {
replaceToken(key, value.invoke())
}
fun replaceToken(key: String, value: Any) {
replacements[key] = value.toString()
}
fun replacements(): Map<String, String> {
return replacements
}
}
open class GenerateResourceTemplates : GenerateAnyTemplates()
open class GenerateSourceTemplates : GenerateAnyTemplates()

View File

@@ -1,34 +1,15 @@
plugins {
id("floodgate.shadow-conventions")
id("com.jfrog.artifactory")
id("maven-publish")
id("net.kyori.indra.publishing")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = project.group as String
artifactId = project.name
version = project.version as String
artifact(tasks["shadowJar"])
artifact(tasks["sourcesJar"])
indra {
configurePublications {
if (shouldAddBranchName()) {
version = versionWithBranchName()
}
}
}
artifactory {
setContextUrl("https://repo.opencollab.dev/artifactory")
publish {
repository {
setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases")
setMavenCompatible(true)
}
defaults {
publications("mavenJava")
setPublishArtifacts(true)
setPublishPom(true)
setPublishIvy(false)
}
}
publishSnapshotsTo("geysermc", "https://repo.opencollab.dev/maven-snapshots")
publishReleasesTo("geysermc", "https://repo.opencollab.dev/maven-releases")
}

View File

@@ -31,6 +31,11 @@ tasks {
// for example Velocity, the relocation will be gone for Velocity)
addRelocations(project, sJar)
}
val destinationDir = System.getenv("DESTINATION_DIRECTORY");
if (destinationDir != null) {
destinationDirectory.set(file(destinationDir))
}
}
named("build") {
dependsOn(shadowJar)

View File

@@ -6,7 +6,7 @@ plugins {
allprojects {
group = "org.geysermc.floodgate"
version = "2.2.0-SNAPSHOT"
version = property("version")!!
description = "Allows Bedrock players to join Java edition servers while keeping the server in online mode"
}

View File

@@ -49,27 +49,21 @@ public final class BungeeInjector extends CommonPlatformInjector {
@Getter private boolean injected;
@Override
public boolean inject() {
try {
// Can everyone just switch to Velocity please :)
public void inject() {
// Can everyone just switch to Velocity please :)
Field framePrepender = ReflectionUtils.getField(PipelineUtils.class, "framePrepender");
Field framePrepender = ReflectionUtils.getField(PipelineUtils.class, "framePrepender");
// Required in order to inject into both Geyser <-> proxy AND proxy <-> server
// (Instead of just replacing the ChannelInitializer which is only called for
// player <-> proxy)
BungeeCustomPrepender customPrepender = new BungeeCustomPrepender(
this, ReflectionUtils.castedStaticValue(framePrepender)
);
// Required in order to inject into both Geyser <-> proxy AND proxy <-> server
// (Instead of just replacing the ChannelInitializer which is only called for
// player <-> proxy)
BungeeCustomPrepender customPrepender = new BungeeCustomPrepender(
this, ReflectionUtils.castedStaticValue(framePrepender)
);
BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender);
BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender);
injected = true;
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
injected = true;
}
@Override
@@ -78,9 +72,9 @@ public final class BungeeInjector extends CommonPlatformInjector {
}
@Override
public boolean removeInjection() {
logger.error("Floodgate cannot remove itself from Bungee without a reboot");
return false;
public void removeInjection() {
throw new IllegalStateException(
"Floodgate cannot remove itself from Bungee without a reboot");
}
void injectClient(Channel channel, boolean clientToProxy) {

View File

@@ -48,7 +48,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData;
import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.ReflectionUtils;
@@ -131,7 +131,7 @@ public final class BungeeListener implements Listener {
if (!config.isSendFloodgateData()) {
FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player != null && !player.isLinked()) {
skinApplier.applySkin(player, new SkinData("", ""));
skinApplier.applySkin(player, new SkinDataImpl("", ""));
}
}
}

View File

@@ -32,6 +32,8 @@ import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Listener;
@@ -66,6 +68,9 @@ public final class BungeePlatformModule extends AbstractModule {
@Override
protected void configure() {
bind(PlatformUtils.class).to(BungeePlatformUtils.class);
bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger());
bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class);
bind(SkinApplier.class).to(BungeeSkinApplier.class);
}
@Provides
@@ -74,12 +79,6 @@ public final class BungeePlatformModule extends AbstractModule {
return plugin;
}
@Provides
@Singleton
public FloodgateLogger floodgateLogger(LanguageManager languageManager) {
return new JavaUtilFloodgateLogger(plugin.getLogger(), languageManager);
}
/*
Commands / Listeners
*/
@@ -123,12 +122,6 @@ public final class BungeePlatformModule extends AbstractModule {
return new BungeePluginMessageRegistration();
}
@Provides
@Singleton
public SkinApplier skinApplier(FloodgateLogger logger) {
return new BungeeSkinApplier(logger);
}
/*
DebugAddon / PlatformInjector
*/

View File

@@ -53,21 +53,6 @@ public final class BungeePluginMessageUtils extends PluginMessageUtils implement
return;
}
UUID targetUuid = null;
String targetUsername = null;
Identity targetIdentity = Identity.UNKNOWN;
Connection target = event.getReceiver();
if (target instanceof ProxiedPlayer) {
ProxiedPlayer player = (ProxiedPlayer) target;
targetUuid = player.getUniqueId();
targetUsername = player.getName();
targetIdentity = Identity.PLAYER;
} else if (target instanceof ServerConnection) {
targetIdentity = Identity.SERVER;
}
UUID sourceUuid = null;
String sourceUsername = null;
Identity sourceIdentity = Identity.UNKNOWN;
@@ -83,8 +68,9 @@ public final class BungeePluginMessageUtils extends PluginMessageUtils implement
sourceIdentity = Identity.SERVER;
}
Result result = channel.handleProxyCall(event.getData(), targetUuid, targetUsername,
targetIdentity, sourceUuid, sourceUsername, sourceIdentity);
Result result = channel.handleProxyCall(
event.getData(), sourceUuid, sourceUsername, sourceIdentity
);
event.setCancelled(!result.isAllowed());

View File

@@ -26,61 +26,46 @@
package org.geysermc.floodgate.pluginmessage;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor;
import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType;
import static org.geysermc.floodgate.util.ReflectionUtils.getMethodByName;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.Property;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.skin.SkinApplyEventImpl;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData;
import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.ReflectionUtils;
@RequiredArgsConstructor
@Singleton
public final class BungeeSkinApplier implements SkinApplier {
private static final Constructor<?> LOGIN_RESULT_CONSTRUCTOR;
private static final Field LOGIN_RESULT_FIELD;
private static final Method SET_PROPERTIES_METHOD;
private static final Class<?> PROPERTY_CLASS;
private static final Constructor<?> PROPERTY_CONSTRUCTOR;
static {
PROPERTY_CLASS = ReflectionUtils.getClassOrFallbackPrefixed(
"protocol.Property", "connection.LoginResult$Property"
);
LOGIN_RESULT_CONSTRUCTOR = getConstructor(
LoginResult.class, true,
String.class, String.class, Array.newInstance(PROPERTY_CLASS, 0).getClass()
);
LOGIN_RESULT_FIELD = getFieldOfType(InitialHandler.class, LoginResult.class);
checkNotNull(LOGIN_RESULT_FIELD, "LoginResult field cannot be null");
SET_PROPERTIES_METHOD = getMethodByName(LoginResult.class, "setProperties", true);
PROPERTY_CONSTRUCTOR = ReflectionUtils.getConstructor(
PROPERTY_CLASS, true,
String.class, String.class, String.class
);
checkNotNull(PROPERTY_CONSTRUCTOR, "Property constructor cannot be null");
}
private final FloodgateLogger logger;
private final ProxyServer server = ProxyServer.getInstance();
@Inject private EventBus eventBus;
@Inject private FloodgateLogger logger;
@Override
public void applySkin(FloodgatePlayer uuid, SkinData skinData) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId());
public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) {
ProxiedPlayer player = server.getPlayer(floodgatePlayer.getCorrectUniqueId());
if (player == null) {
return;
}
@@ -97,21 +82,46 @@ public final class BungeeSkinApplier implements SkinApplier {
// expected to be null since LoginResult is the data from hasJoined,
// which Floodgate players don't have
if (loginResult == null) {
// id and name are unused and properties will be overridden
loginResult = (LoginResult) ReflectionUtils.newInstance(
LOGIN_RESULT_CONSTRUCTOR, null, null, null
);
// id and name are unused
loginResult = new LoginResult(null, null, new Property[0]);
ReflectionUtils.setValue(handler, LOGIN_RESULT_FIELD, loginResult);
}
Object property = ReflectionUtils.newInstance(
PROPERTY_CONSTRUCTOR,
"textures", skinData.getValue(), skinData.getSignature()
);
Property[] properties = loginResult.getProperties();
Object propertyArray = Array.newInstance(PROPERTY_CLASS, 1);
Array.set(propertyArray, 0, property);
SkinData currentSkin = currentSkin(properties);
ReflectionUtils.invoke(loginResult, SET_PROPERTIES_METHOD, propertyArray);
SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData);
event.setCancelled(floodgatePlayer.isLinked());
eventBus.fire(event);
if (event.isCancelled()) {
return;
}
loginResult.setProperties(replaceSkin(properties, event.newSkin()));
}
private SkinData currentSkin(Property[] properties) {
for (Property property : properties) {
if (property.getName().equals("textures")) {
if (!property.getValue().isEmpty()) {
return new SkinDataImpl(property.getValue(), property.getSignature());
}
}
}
return null;
}
private Property[] replaceSkin(Property[] properties, SkinData skinData) {
List<Property> list = new ArrayList<>();
for (Property property : properties) {
if (!property.getName().equals("textures")) {
list.add(property);
}
}
list.add(new Property("textures", skinData.value(), skinData.signature()));
return list.toArray(new Property[0]);
}
}

View File

@@ -1,20 +1,23 @@
import net.kyori.blossom.BlossomExtension
plugins {
id("net.kyori.blossom")
id("floodgate.generate-templates")
}
dependencies {
api(projects.api)
api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion)
compileOnly(projects.ap)
annotationProcessor(projects.ap)
api("com.google.inject", "guice", Versions.guiceVersion)
api("com.nukkitx.fastutil", "fastutil-short-object-maps", Versions.fastutilVersion)
api("com.nukkitx.fastutil", "fastutil-int-object-maps", Versions.fastutilVersion)
api("org.java-websocket", "Java-WebSocket", Versions.javaWebsocketVersion)
api("cloud.commandframework", "cloud-core", Versions.cloudVersion)
api("org.yaml", "snakeyaml", Versions.snakeyamlVersion)
api("org.bstats", "bstats-base", Versions.bstatsVersion)
//todo use official dependency once https://github.com/Bastian/bstats-metrics/pull/118 is merged
api("com.github.Konicai.bstats-metrics", "bstats-base", Versions.bstatsVersion)
}
// present on all platforms
@@ -23,9 +26,10 @@ provided("io.netty", "netty-codec", Versions.nettyVersion)
relocate("org.bstats")
configure<BlossomExtension> {
val constantsFile = "src/main/java/org/geysermc/floodgate/util/Constants.java"
replaceToken("\${floodgateVersion}", fullVersion(), constantsFile)
replaceToken("\${branch}", branchName(), constantsFile)
replaceToken("\${buildNumber}", buildNumber(), constantsFile)
tasks {
templateSources {
replaceToken("floodgateVersion", fullVersion())
replaceToken("branch", branchName())
replaceToken("buildNumber", buildNumber())
}
}

View File

@@ -28,121 +28,66 @@ package org.geysermc.floodgate;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.name.Named;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.module.ConfigLoadedModule;
import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.lifecycle.PostEnableEvent;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.module.PostInitializeModule;
import org.geysermc.floodgate.news.NewsChecker;
import org.geysermc.floodgate.util.Metrics;
import org.geysermc.floodgate.util.PrefixCheckTask;
public class FloodgatePlatform {
private static final UUID KEY = UUID.randomUUID();
private final FloodgateApi api;
private final PlatformInjector injector;
@Inject private PlatformInjector injector;
private final FloodgateLogger logger;
private FloodgateConfig config;
private Injector guice;
@Inject
public FloodgatePlatform(
FloodgateApi api,
PlatformInjector platformInjector,
FloodgateLogger logger,
Injector guice) {
this.api = api;
this.injector = platformInjector;
this.logger = logger;
this.guice = guice;
}
@Inject private FloodgateConfig config;
@Inject private Injector guice;
@Inject
public void init(
@Named("dataDirectory") Path dataDirectory,
ConfigLoader configLoader,
FloodgateConfigHolder configHolder,
FloodgateApi api,
PlayerLink link,
FloodgateEventBus eventBus,
PacketHandlers packetHandlers,
HandshakeHandlers handshakeHandlers) {
if (!Files.isDirectory(dataDirectory)) {
try {
Files.createDirectory(dataDirectory);
} catch (IOException exception) {
logger.error("Failed to create the data folder", exception);
throw new RuntimeException("Failed to create the data folder", exception);
}
}
config = configLoader.load();
if (config.isDebug()) {
logger.enableDebug();
}
configHolder.set(config);
guice = guice.createChildInjector(new ConfigLoadedModule(config));
PlayerLink link = guice.getInstance(PlayerLinkLoader.class).load();
InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY);
guice.getInstance(NewsChecker.class).start();
HandshakeHandlers handshakeHandlers
) {
InstanceHolder.set(
api, link, eventBus, this.injector, packetHandlers, handshakeHandlers, KEY
);
}
public boolean enable(Module... postInitializeModules) {
public void enable(Module... postInitializeModules) throws RuntimeException {
if (injector == null) {
logger.error("Failed to find the platform injector!");
return false;
throw new RuntimeException("Failed to find the platform injector!");
}
try {
if (!injector.inject()) {
logger.error("Failed to inject the packet listener!");
return false;
}
injector.inject();
} catch (Exception exception) {
logger.error("Failed to inject the packet listener!", exception);
return false;
throw new RuntimeException("Failed to inject the packet listener!", exception);
}
this.guice = guice.createChildInjector(new PostInitializeModule(postInitializeModules));
PrefixCheckTask.checkAndExecuteDelayed(config, logger);
guice.getInstance(Metrics.class);
return true;
guice.getInstance(EventBus.class).fire(new PostEnableEvent());
}
public boolean disable() {
public void disable() {
guice.getInstance(EventBus.class).fire(new ShutdownEvent());
if (injector != null && injector.canRemoveInjection()) {
try {
if (!injector.removeInjection()) {
logger.error("Failed to remove the injection!");
}
injector.removeInjection();
} catch (Exception exception) {
logger.error("Failed to remove the injection!", exception);
throw new RuntimeException("Failed to remove the injection!", exception);
}
}
guice.getInstance(NewsChecker.class).shutdown();
api.getPlayerLink().stop();
return true;
}
public boolean isProxy() {

View File

@@ -25,24 +25,14 @@
package org.geysermc.floodgate.api;
import com.google.inject.Inject;
import java.nio.charset.StandardCharsets;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.util.BedrockData;
public final class ProxyFloodgateApi extends SimpleFloodgateApi {
private final FloodgateCipher cipher;
public ProxyFloodgateApi(
PluginMessageManager pluginMessageManager,
FloodgateConfigHolder configHolder,
FloodgateLogger logger,
FloodgateCipher cipher) {
super(pluginMessageManager, configHolder, logger);
this.cipher = cipher;
}
@Inject
private FloodgateCipher cipher;
public byte[] createEncryptedData(BedrockData bedrockData) {
try {

View File

@@ -30,41 +30,41 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.unsafe.Unsafe;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.pluginmessage.channel.FormChannel;
import org.geysermc.floodgate.pluginmessage.channel.TransferChannel;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpClient;
import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor
public class SimpleFloodgateApi implements FloodgateApi {
private final Map<UUID, FloodgatePlayer> players = new HashMap<>();
private final Map<UUID, FloodgatePlayer> players = new ConcurrentHashMap<>();
private final Cache<UUID, FloodgatePlayer> pendingRemove =
CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.build();
private final PluginMessageManager pluginMessageManager;
private final FloodgateConfigHolder configHolder;
private final FloodgateLogger logger;
@Inject private PluginMessageManager pluginMessageManager;
@Inject private FloodgateConfig config;
@Inject private HttpClient httpClient;
@Inject private FloodgateLogger logger;
@Override
public String getPlayerPrefix() {
return configHolder.get().getUsernamePrefix();
return config.getUsernamePrefix();
}
@Override
@@ -148,7 +148,7 @@ public class SimpleFloodgateApi implements FloodgateApi {
return Utils.failedFuture(new IllegalStateException("Received an invalid gamertag"));
}
return HttpUtils.asyncGet(Constants.GET_XUID_URL + gamertag)
return httpClient.asyncGet(Constants.GET_XUID_URL + gamertag)
.thenApply(result -> {
JsonObject response = result.getResponse();
@@ -163,7 +163,7 @@ public class SimpleFloodgateApi implements FloodgateApi {
@Override
public CompletableFuture<String> getGamertagFor(long xuid) {
return HttpUtils.asyncGet(Constants.GET_GAMERTAG_URL + xuid)
return httpClient.asyncGet(Constants.GET_GAMERTAG_URL + xuid)
.thenApply(result -> {
JsonObject response = result.getResponse();

View File

@@ -49,10 +49,11 @@ import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.audience.ProfileAudience;
import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpClient;
public class WhitelistCommand implements FloodgateCommand {
@Inject private FloodgateConfig config;
@Inject private HttpClient httpClient;
@Inject private FloodgateLogger logger;
@Override
@@ -128,7 +129,7 @@ public class WhitelistCommand implements FloodgateCommand {
final String strippedName = name;
// We need to get the UUID of the player if it's not manually specified
HttpUtils.asyncGet(Constants.GET_XUID_URL + name)
httpClient.asyncGet(Constants.GET_XUID_URL + name)
.whenComplete((result, error) -> {
if (error != null) {
sender.sendMessage(Message.API_UNAVAILABLE);

View File

@@ -29,20 +29,40 @@ import static org.geysermc.floodgate.util.Constants.COLOR_CHAR;
import cloud.commandframework.context.CommandContext;
import com.google.gson.JsonElement;
import com.google.inject.Inject;
import it.unimi.dsi.fastutil.Pair;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.HttpClient;
import org.geysermc.floodgate.util.HttpClient.HttpResponse;
import org.geysermc.floodgate.util.Utils;
final class FirewallCheckSubcommand {
private FirewallCheckSubcommand() {}
final class FirewallCheckSubcommand extends FloodgateSubCommand {
@Inject
private HttpClient httpClient;
static void executeFirewall(CommandContext<UserAudience> context) {
@Override
public String name() {
return "firewall";
}
@Override
public String description() {
return "Check if your outgoing firewall allows Floodgate to work properly";
}
@Override
public Permission permission() {
return Permission.COMMAND_MAIN_FIREWALL;
}
@Override
public void execute(CommandContext<UserAudience> context) {
UserAudience sender = context.getSender();
executeChecks(
globalApiCheck(sender)
@@ -54,12 +74,12 @@ final class FirewallCheckSubcommand {
);
}
private static BooleanSupplier globalApiCheck(UserAudience sender) {
private BooleanSupplier globalApiCheck(UserAudience sender) {
return executeFirewallText(
sender, "global api",
() -> {
HttpResponse<JsonElement> response =
HttpUtils.get(Constants.HEALTH_URL, JsonElement.class);
httpClient.get(Constants.HEALTH_URL, JsonElement.class);
if (!response.isCodeOk()) {
throw new IllegalStateException(String.format(
@@ -70,7 +90,7 @@ final class FirewallCheckSubcommand {
});
}
private static BooleanSupplier executeFirewallText(
private BooleanSupplier executeFirewallText(
UserAudience sender, String name, Runnable runnable) {
return () -> {
sender.sendMessage(COLOR_CHAR + "eTesting " + name + "...");
@@ -86,9 +106,7 @@ final class FirewallCheckSubcommand {
};
}
private static CompletableFuture<Pair<Integer, Integer>> executeChecks(
BooleanSupplier... checks) {
private CompletableFuture<Pair<Integer, Integer>> executeChecks(BooleanSupplier... checks) {
return CompletableFuture.supplyAsync(() -> {
AtomicInteger okCount = new AtomicInteger();
AtomicInteger failCount = new AtomicInteger();

View File

@@ -33,13 +33,18 @@ import cloud.commandframework.Command.Builder;
import cloud.commandframework.CommandManager;
import cloud.commandframework.context.CommandContext;
import java.util.Locale;
import java.util.function.Consumer;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.platform.command.SubCommands;
import org.geysermc.floodgate.player.UserAudience;
public final class MainCommand implements FloodgateCommand {
public final class MainCommand extends SubCommands implements FloodgateCommand {
public MainCommand() {
defineSubCommand(FirewallCheckSubcommand.class);
defineSubCommand(VersionSubcommand.class);
}
@Override
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
Builder<UserAudience> builder = commandManager.commandBuilder(
@@ -49,11 +54,11 @@ public final class MainCommand implements FloodgateCommand {
.permission(Permission.COMMAND_MAIN.get())
.handler(this::execute);
for (SubCommand subCommand : SubCommand.VALUES) {
for (FloodgateSubCommand subCommand : subCommands()) {
commandManager.command(builder
.literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description)
.permission(subCommand.permission.get())
.handler(subCommand.executor::accept)
.literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description())
.permission(subCommand.permission().get())
.handler(subCommand::execute)
);
}
@@ -65,27 +70,15 @@ public final class MainCommand implements FloodgateCommand {
public void execute(CommandContext<UserAudience> context) {
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
for (SubCommand subCommand : SubCommand.VALUES) {
if (context.getSender().hasPermission(subCommand.permission.get())) {
for (FloodgateSubCommand subCommand : subCommands()) {
if (context.getSender().hasPermission(subCommand.permission().get())) {
helpMessage.append('\n').append(COLOR_CHAR).append('b')
.append(subCommand.name().toLowerCase(Locale.ROOT))
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
.append(subCommand.description);
.append(subCommand.description());
}
}
context.getSender().sendMessage(helpMessage.toString());
}
@RequiredArgsConstructor
enum SubCommand {
FIREWALL("Check if your outgoing firewall allows Floodgate to work properly",
Permission.COMMAND_MAIN_FIREWALL, FirewallCheckSubcommand::executeFirewall);
static final SubCommand[] VALUES = values();
final String description;
final Permission permission;
final Consumer<CommandContext<UserAudience>> executor;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.command.main;
import static org.geysermc.floodgate.util.Constants.COLOR_CHAR;
import cloud.commandframework.context.CommandContext;
import com.google.gson.JsonElement;
import com.google.inject.Inject;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.WhitelistCommand.Message;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpClient;
public class VersionSubcommand extends FloodgateSubCommand {
@Inject
private HttpClient httpClient;
@Inject
private FloodgateLogger logger;
@Override
public String name() {
return "version";
}
@Override
public String description() {
return "Displays version information about Floodgate";
}
@Override
public Permission permission() {
return Permission.COMMAND_MAIN_VERSION;
}
@Override
public void execute(CommandContext<UserAudience> context) {
UserAudience sender = context.getSender();
sender.sendMessage(String.format(
COLOR_CHAR + "7You're currently on " + COLOR_CHAR + "b%s" +
COLOR_CHAR + "7 (branch: " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7)\n" +
COLOR_CHAR + "eFetching latest build info...",
Constants.VERSION, Constants.GIT_BRANCH
));
String baseUrl = String.format(
"https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/%s/lastSuccessfulBuild/",
Constants.GIT_BRANCH
);
httpClient.asyncGet(
baseUrl + "buildNumber",
JsonElement.class
).whenComplete((result, error) -> {
if (error != null) {
sender.sendMessage(COLOR_CHAR + "cCould not retrieve latest version info!");
error.printStackTrace();
return;
}
JsonElement response = result.getResponse();
logger.info(String.valueOf(response));
logger.info("{}", result.getHttpCode());
if (result.getHttpCode() == 404) {
sender.sendMessage(
COLOR_CHAR + "cGot a 404 (not found) while requesting the latest version." +
" Are you using a custom Floodgate version?"
);
return;
}
if (!result.isCodeOk()) {
//todo make it more generic instead of using a Whitelist command strings
logger.error(
"Got an error from requesting the latest Floodgate version: {}",
response.toString()
);
sender.sendMessage(Message.UNEXPECTED_ERROR);
return;
}
int buildNumber = response.getAsInt();
if (buildNumber > Constants.BUILD_NUMBER) {
sender.sendMessage(String.format(
COLOR_CHAR + "7There is a newer version of Floodgate available!\n" +
COLOR_CHAR + "7You are " + COLOR_CHAR + "e%s " + COLOR_CHAR + "7builds behind.\n" +
COLOR_CHAR + "7Download the latest Floodgate version here: " + COLOR_CHAR + "b%s",
buildNumber - Constants.BUILD_NUMBER, baseUrl
));
return;
}
if (buildNumber == Constants.BUILD_NUMBER) {
sender.sendMessage(COLOR_CHAR + "aYou're running the latest version of Floodgate!");
return;
}
sender.sendMessage(COLOR_CHAR + "cCannot check version for custom Floodgate versions!");
});
}
}

View File

@@ -31,6 +31,7 @@ import static org.geysermc.floodgate.command.util.PermissionDefault.TRUE;
public enum Permission {
COMMAND_MAIN("floodgate.command.floodgate", TRUE),
COMMAND_MAIN_FIREWALL(COMMAND_MAIN, "firewall", OP),
COMMAND_MAIN_VERSION(COMMAND_MAIN, "version", OP),
COMMAND_LINK("floodgate.command.linkaccount", TRUE),
COMMAND_UNLINK("floodgate.command.unlinkaccount", TRUE),
COMMAND_WHITELIST("floodgate.command.fwhitelist", OP),

View File

@@ -35,21 +35,18 @@ import org.geysermc.configutils.ConfigUtilities;
import org.geysermc.configutils.file.codec.PathFileCodec;
import org.geysermc.configutils.file.template.ResourceTemplateReader;
import org.geysermc.configutils.updater.change.Changes;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer;
@Getter
@RequiredArgsConstructor
public final class ConfigLoader {
private final Path dataFolder;
private final Path dataDirectory;
private final Class<? extends FloodgateConfig> configClass;
private final KeyProducer keyProducer;
private final FloodgateCipher cipher;
private final FloodgateLogger logger;
@SuppressWarnings("unchecked")
public <T extends FloodgateConfig> T load() {
String templateFile = "config.yml";
@@ -65,7 +62,7 @@ public final class ConfigLoader {
ConfigUtilities utilities =
ConfigUtilities.builder()
.fileCodec(PathFileCodec.of(dataFolder))
.fileCodec(PathFileCodec.of(dataDirectory))
.configFile("config.yml")
.templateReader(ResourceTemplateReader.of(getClass()))
.template(templateFile)
@@ -100,21 +97,16 @@ public final class ConfigLoader {
String decrypted = cipher.decryptToString(encrypted);
if (!test.equals(decrypted)) {
logger.error("Whoops, we tested the generated Floodgate keys but " +
"the decrypted test message doesn't match the original.\n" +
throw new RuntimeException("Failed to decrypt test message.\n" +
"Original message: " + test + "." +
"Decrypted message: " + decrypted + ".\n" +
"The encrypted message itself: " + new String(encrypted)
);
throw new RuntimeException(
"Tested the generated public and private key but, " +
"the decrypted message doesn't match the original!"
);
}
Files.write(keyPath, key.getEncoded());
} catch (Exception exception) {
logger.error("Error while creating key", exception);
throw new RuntimeException("Error while creating key", exception);
}
}
}

View File

@@ -52,7 +52,9 @@ public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoad
private boolean debug;
private int configVersion;
private Key key;
private String rawUsernamePrefix;
public boolean isProxy() {
return this instanceof ProxyFloodgateConfig;
@@ -60,7 +62,7 @@ public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoad
@Override
public CallbackResult postInitialize(ConfigLoader loader) {
Path keyPath = loader.getDataFolder().resolve(getKeyFileName());
Path keyPath = loader.getDataDirectory().resolve(getKeyFileName());
// don't assume that the key always exists with the existence of a config
if (!Files.exists(keyPath)) {
@@ -74,6 +76,14 @@ public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoad
} catch (IOException exception) {
return CallbackResult.failed(exception.getMessage());
}
rawUsernamePrefix = usernamePrefix;
// Java usernames can't be longer than 16 chars
if (usernamePrefix.length() >= 16) {
usernamePrefix = ".";
}
return CallbackResult.ok();
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event;
import com.google.inject.Singleton;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.PostOrder;
import org.geysermc.event.bus.impl.EventBusImpl;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.event.subscribe.Subscriber;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
@Singleton
@SuppressWarnings("unchecked")
public final class EventBus extends EventBusImpl<Object, FloodgateSubscriber<?>>
implements FloodgateEventBus {
@Override
protected <H, T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass,
@NonNull Subscribe subscribe,
@NonNull H listener,
@NonNull BiConsumer<H, T> handler
) {
return (B) new EventSubscriber<>(
eventClass, subscribe.postOrder(), subscribe.ignoreCancelled(), listener, handler
);
}
@Override
protected <T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass,
@NonNull Consumer<T> handler,
@NonNull PostOrder postOrder
) {
return (B) new EventSubscriber<>(eventClass, handler, postOrder);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.PostOrder;
import org.geysermc.event.subscribe.impl.SubscriberImpl;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
public final class EventSubscriber<E> extends SubscriberImpl<E> implements FloodgateSubscriber<E> {
EventSubscriber(
@NonNull Class<E> eventClass,
@NonNull Consumer<E> handler,
@NonNull PostOrder postOrder
) {
super(eventClass, handler, postOrder);
}
<H> EventSubscriber(
Class<E> eventClass,
PostOrder postOrder,
boolean ignoreCancelled,
H handlerInstance,
BiConsumer<H, E> handler
) {
super(eventClass, postOrder, ignoreCancelled, handlerInstance, handler);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event.lifecycle;
public class PostEnableEvent {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event.lifecycle;
public class ShutdownEvent {
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event.skin;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.event.util.AbstractCancellable;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public class SkinApplyEventImpl extends AbstractCancellable implements SkinApplyEvent {
private final FloodgatePlayer player;
private final SkinData currentSkin;
private SkinData newSkin;
public SkinApplyEventImpl(
@NonNull FloodgatePlayer player,
@Nullable SkinData currentSkin,
@NonNull SkinData newSkin
) {
this.player = Objects.requireNonNull(player);
this.currentSkin = currentSkin;
this.newSkin = Objects.requireNonNull(newSkin);
}
@Override
public @NonNull FloodgatePlayer player() {
return player;
}
public @Nullable SkinData currentSkin() {
return currentSkin;
}
public @NonNull SkinData newSkin() {
return newSkin;
}
public SkinApplyEventImpl newSkin(@NonNull SkinData skinData) {
this.newSkin = Objects.requireNonNull(skinData);
return this;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.event.util;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.AbstractMatcher;
import org.geysermc.event.Listener;
public class ListenerAnnotationMatcher extends AbstractMatcher<TypeLiteral<?>> {
@Override
public boolean matches(TypeLiteral<?> typeLiteral) {
Class<?> rawType = typeLiteral.getRawType();
return rawType.isAnnotationPresent(Listener.class);
}
}

View File

@@ -26,18 +26,17 @@
package org.geysermc.floodgate.inject;
import io.netty.channel.Channel;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Getter;
import java.util.WeakHashMap;
import org.geysermc.floodgate.api.inject.InjectorAddon;
import org.geysermc.floodgate.api.inject.PlatformInjector;
public abstract class CommonPlatformInjector implements PlatformInjector {
@Getter(AccessLevel.PROTECTED)
private final Set<Channel> injectedClients = new HashSet<>();
private final Set<Channel> injectedClients =
Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
private final Map<Class<?>, InjectorAddon> addons = new HashMap<>();
@@ -49,6 +48,10 @@ public abstract class CommonPlatformInjector implements PlatformInjector {
return injectedClients.remove(channel);
}
public Set<Channel> injectedClients() {
return injectedClients;
}
@Override
public boolean addAddon(InjectorAddon addon) {
return addons.putIfAbsent(addon.getClass(), addon) == null;

View File

@@ -34,6 +34,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.AccessLevel;
import lombok.Getter;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.link.LinkRequest;
import org.geysermc.floodgate.api.link.PlayerLink;
@@ -41,11 +43,13 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.database.config.DatabaseConfig;
import org.geysermc.floodgate.database.config.DatabaseConfigLoader;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.util.InjectorHolder;
@Listener
public abstract class CommonPlayerLink implements PlayerLink {
@Getter(AccessLevel.PROTECTED)
private final ExecutorService executorService = Executors.newFixedThreadPool(11);
private final ExecutorService executorService = Executors.newCachedThreadPool();
@Getter private boolean enabled;
@Getter private boolean allowLinking;
@@ -102,4 +106,9 @@ public abstract class CommonPlayerLink implements PlayerLink {
public void stop() {
executorService.shutdown();
}
@Subscribe
public void onShutdown(ShutdownEvent ignored) {
stop();
}
}

View File

@@ -29,19 +29,23 @@ import static org.geysermc.floodgate.util.Constants.GET_BEDROCK_LINK;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.DefaultHttpResponse;
import org.geysermc.floodgate.util.HttpClient;
import org.geysermc.floodgate.util.HttpClient.DefaultHttpResponse;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.Utils;
@Getter
public class GlobalPlayerLinking extends CommonPlayerLink {
@Inject
private HttpClient httpClient;
private PlayerLink databaseImpl;
public void setDatabaseImpl(PlayerLink databaseImpl) {
@@ -94,7 +98,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
return CompletableFuture.supplyAsync(
() -> {
DefaultHttpResponse response =
HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
// either the global api is down or it failed to return link
if (!response.isCodeOk()) {
@@ -144,7 +148,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
return CompletableFuture.supplyAsync(
() -> {
DefaultHttpResponse response =
HttpUtils.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
httpClient.get(GET_BEDROCK_LINK + bedrockId.getLeastSignificantBits());
if (!response.isCodeOk()) {
getLogger().error(

View File

@@ -32,6 +32,7 @@ import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import java.io.IOException;
import java.io.InputStream;
@@ -42,18 +43,23 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.inject.Named;
import java.util.stream.Stream;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.PlayerLinkConfig;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.InjectorHolder;
import org.geysermc.floodgate.util.Utils;
@Listener
@Singleton
@SuppressWarnings("unchecked")
public final class PlayerLinkLoader {
public final class PlayerLinkHolder {
@Inject private Injector injector;
@Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
@@ -62,30 +68,38 @@ public final class PlayerLinkLoader {
@Named("dataDirectory")
private Path dataDirectory;
@Nonnull
private URLClassLoader classLoader;
private PlayerLink instance;
@NonNull
public PlayerLink load() {
if (instance != null) {
return instance;
}
if (config == null) {
throw new IllegalStateException("Config cannot be null!");
}
FloodgateConfig.PlayerLinkConfig lConfig = config.getPlayerLink();
if (!lConfig.isEnabled()) {
PlayerLinkConfig linkConfig = config.getPlayerLink();
if (!linkConfig.isEnabled()) {
return new DisabledPlayerLink();
}
List<Path> files;
try {
files = Files.list(dataDirectory)
.filter(path -> Files.isRegularFile(path) && path.toString().endsWith("jar"))
try (Stream<Path> list = Files.list(dataDirectory)) {
files = list
.filter(path -> Files.isRegularFile(path) && path.toString().endsWith(".jar"))
.collect(Collectors.toList());
} catch (IOException exception) {
logger.error("Failed to list possible database implementations", exception);
return new DisabledPlayerLink();
}
// we can skip the rest if global linking is enabled and no database implementations has been
// found, or when global linking is enabled and own player linking is disabled.
if (lConfig.isEnableGlobalLinking() && (files.isEmpty() || !lConfig.isEnableOwnLinking())) {
// we can skip the rest if global linking is enabled and no database implementations has
// been found, or when global linking is enabled and own player linking is disabled.
if (linkConfig.isEnableGlobalLinking() &&
(files.isEmpty() || !linkConfig.isEnableOwnLinking())) {
return injector.getInstance(GlobalPlayerLinking.class);
}
@@ -100,7 +114,7 @@ public final class PlayerLinkLoader {
// We only want to load one database implementation
if (files.size() > 1) {
boolean found = false;
databaseName = lConfig.getType();
databaseName = linkConfig.getType();
String expectedName = "floodgate-" + databaseName + "-database.jar";
for (Path path : files) {
@@ -111,14 +125,18 @@ public final class PlayerLinkLoader {
}
if (!found) {
logger.error("Failed to find an implementation for type: {}", lConfig.getType());
logger.error(
"Failed to find an implementation for type: {}", linkConfig.getType()
);
return new DisabledPlayerLink();
}
} else {
String name = implementationPath.getFileName().toString();
if (!Utils.isValidDatabaseName(name)) {
logger.error("Found database {} but the name doesn't match {}",
name, Constants.DATABASE_NAME_FORMAT);
logger.error(
"Found database {} but the name doesn't match {}",
name, Constants.DATABASE_NAME_FORMAT
);
return new DisabledPlayerLink();
}
int firstSplit = name.indexOf('-') + 1;
@@ -133,24 +151,22 @@ public final class PlayerLinkLoader {
// we don't have a way to close this properly since we have no stop method and we have
// to be able to load classes on the fly, but that doesn't matter anyway since Floodgate
// doesn't support reloading
URLClassLoader classLoader = new URLClassLoader(
classLoader = new URLClassLoader(
new URL[]{pluginUrl},
PlayerLinkLoader.class.getClassLoader()
PlayerLinkHolder.class.getClassLoader()
);
String mainClassName;
JsonObject linkConfig;
try (InputStream linkConfigStream =
classLoader.getResourceAsStream("init.json")) {
JsonObject dbInitConfig;
try (InputStream linkConfigStream = classLoader.getResourceAsStream("init.json")) {
requireNonNull(linkConfigStream, "Implementation should have an init file");
linkConfig = new Gson().fromJson(
dbInitConfig = new Gson().fromJson(
new InputStreamReader(linkConfigStream), JsonObject.class
);
mainClassName = linkConfig.get("mainClass").getAsString();
mainClassName = dbInitConfig.get("mainClass").getAsString();
}
Class<? extends PlayerLink> mainClass =
@@ -167,16 +183,16 @@ public final class PlayerLinkLoader {
Names.named("databaseClassLoader")).toInstance(classLoader);
binder.bind(JsonObject.class)
.annotatedWith(Names.named("databaseInitData"))
.toInstance(linkConfig);
.toInstance(dbInitConfig);
binder.bind(InjectorHolder.class)
.toInstance(injectorHolder);
});
injectorHolder.set(linkInjector);
PlayerLink instance = linkInjector.getInstance(mainClass);
instance = linkInjector.getInstance(mainClass);
// we use our own internal PlayerLinking when global linking is enabled
if (lConfig.isEnableGlobalLinking()) {
if (linkConfig.isEnableGlobalLinking()) {
GlobalPlayerLinking linking = linkInjector.getInstance(GlobalPlayerLinking.class);
linking.setDatabaseImpl(instance);
linking.load();
@@ -186,8 +202,10 @@ public final class PlayerLinkLoader {
return instance;
}
} catch (ClassCastException exception) {
logger.error("The database implementation ({}) doesn't extend the PlayerLink class!",
implementationPath.getFileName().toString(), exception);
logger.error(
"The database implementation ({}) doesn't extend the PlayerLink class!",
implementationPath.getFileName().toString(), exception
);
return new DisabledPlayerLink();
} catch (Exception exception) {
if (init) {
@@ -198,4 +216,10 @@ public final class PlayerLinkLoader {
return new DisabledPlayerLink();
}
}
@Subscribe
public void onShutdown(ShutdownEvent ignored) throws Exception {
instance.stop();
classLoader.close();
}
}

View File

@@ -27,17 +27,29 @@ package org.geysermc.floodgate.logger;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.util.logging.Level;
import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor
@Singleton
public final class JavaUtilFloodgateLogger implements FloodgateLogger {
private final Logger logger;
private final LanguageManager languageManager;
private Level originLevel;
@Inject
@Named("logger")
private Logger logger;
private LanguageManager languageManager;
@Inject
private void init(LanguageManager languageManager, FloodgateConfig config) {
this.languageManager = languageManager;
if (config.isDebug()) {
logger.setLevel(Level.ALL);
}
}
@Override
public void error(String message, Object... args) {
@@ -74,19 +86,6 @@ public final class JavaUtilFloodgateLogger implements FloodgateLogger {
logger.finer(format(message, args));
}
@Override
public void enableDebug() {
originLevel = logger.getLevel();
logger.setLevel(Level.ALL);
}
@Override
public void disableDebug() {
if (originLevel != null) {
logger.setLevel(originLevel);
}
}
@Override
public boolean isDebug() {
return logger.getLevel() == Level.ALL;

View File

@@ -23,25 +23,17 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
package org.geysermc.floodgate.module;
import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import com.google.inject.AbstractModule;
import org.geysermc.floodgate.util.AutoBind;
import org.geysermc.floodgate.util.Utils;
@Getter
@RequiredArgsConstructor
public class SkinData {
private final String value;
private final String signature;
public static SkinData from(JsonObject data) {
if (data.has("signature") && !data.get("signature").isJsonNull()) {
return new SkinData(
data.get("value").getAsString(),
data.get("signature").getAsString()
);
public class AutoBindModule extends AbstractModule {
@Override
protected void configure() {
for (Class<?> clazz : Utils.getGeneratedClassesForAnnotation(AutoBind.class)) {
bind(clazz).asEagerSingleton();
}
return new SkinData(data.get("value").getAsString(), null);
}
}

View File

@@ -28,49 +28,105 @@ package org.geysermc.floodgate.module;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import io.netty.util.AttributeKey;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import lombok.RequiredArgsConstructor;
import org.geysermc.event.PostOrder;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer;
import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.event.util.ListenerAnnotationMatcher;
import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.news.NewsChecker;
import org.geysermc.floodgate.link.PlayerLinkHolder;
import org.geysermc.floodgate.packet.PacketHandlersImpl;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpClient;
import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor
public class CommonModule extends AbstractModule {
private final EventBus eventBus = new EventBus();
private final Path dataDirectory;
@Override
protected void configure() {
bind(EventBus.class).toInstance(eventBus);
bind(FloodgateEventBus.class).to(EventBus.class);
// register every class that has the Listener annotation
bindListener(new ListenerAnnotationMatcher(), new TypeListener() {
@Override
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
encounter.register((InjectionListener<I>) eventBus::register);
}
});
ExecutorService commonPool = Executors.newCachedThreadPool();
ScheduledExecutorService commonScheduledPool = Executors.newSingleThreadScheduledExecutor();
eventBus.subscribe(ShutdownEvent.class, ignored -> {
commonPool.shutdown();
commonScheduledPool.shutdown();
}, PostOrder.LAST);
bind(ExecutorService.class)
.annotatedWith(Names.named("commonPool"))
.toInstance(commonPool);
bind(ScheduledExecutorService.class)
.annotatedWith(Names.named("commonScheduledPool"))
.toInstance(commonScheduledPool);
bind(HttpClient.class).in(Singleton.class);
bind(FloodgateApi.class).to(SimpleFloodgateApi.class);
bind(PlatformInjector.class).to(CommonPlatformInjector.class);
bind(HandshakeHandlers.class).to(HandshakeHandlersImpl.class);
bind(HandshakeHandlersImpl.class).in(Singleton.class);
bind(PacketHandlers.class).to(PacketHandlersImpl.class);
bind(PacketHandlersImpl.class).asEagerSingleton();
install(new AutoBindModule());
}
@Provides
@Singleton
public FloodgateConfig floodgateConfig(ConfigLoader configLoader) {
return configLoader.load();
}
@Provides
@Singleton
public PlayerLink playerLink(PlayerLinkHolder linkLoader) {
return linkLoader.load();
}
@Provides
@@ -92,34 +148,13 @@ public class CommonModule extends AbstractModule {
return dataDirectory;
}
@Provides
@Singleton
public FloodgateConfigHolder configHolder() {
return new FloodgateConfigHolder();
}
@Provides
@Singleton
public ConfigLoader configLoader(
@Named("configClass") Class<? extends FloodgateConfig> configClass,
KeyProducer producer,
FloodgateCipher cipher,
FloodgateLogger logger) {
return new ConfigLoader(dataDirectory, configClass, producer, cipher, logger);
}
@Provides
@Singleton
public LanguageManager languageLoader(
FloodgateConfigHolder configHolder,
FloodgateLogger logger) {
return new LanguageManager(configHolder, logger);
}
@Provides
@Singleton
public HandshakeHandlersImpl handshakeHandlers() {
return new HandshakeHandlersImpl();
FloodgateCipher cipher) {
return new ConfigLoader(dataDirectory, configClass, producer, cipher);
}
@Provides
@@ -128,13 +163,14 @@ public class CommonModule extends AbstractModule {
HandshakeHandlersImpl handshakeHandlers,
SimpleFloodgateApi api,
FloodgateCipher cipher,
FloodgateConfigHolder configHolder,
FloodgateConfig config,
SkinUploadManager skinUploadManager,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger) {
FloodgateLogger logger,
LanguageManager languageManager) {
return new FloodgateHandshakeHandler(handshakeHandlers, api, cipher, configHolder,
skinUploadManager, playerAttribute, logger);
return new FloodgateHandshakeHandler(handshakeHandlers, api, cipher, config,
skinUploadManager, playerAttribute, logger, languageManager);
}
@Provides
@@ -145,17 +181,16 @@ public class CommonModule extends AbstractModule {
@Provides
@Singleton
public SkinUploadManager skinUploadManager(
FloodgateApi api,
SkinApplier skinApplier,
FloodgateLogger logger) {
return new SkinUploadManager(api, skinApplier, logger);
@Named("gitBranch")
public String gitBranch() {
return Constants.GIT_BRANCH;
}
@Provides
@Singleton
public NewsChecker newsChecker(CommandUtil commandUtil, FloodgateLogger logger) {
return new NewsChecker(commandUtil, logger, Constants.GIT_BRANCH, Constants.BUILD_NUMBER);
@Named("buildNumber")
public int buildNumber() {
return Constants.BUILD_NUMBER;
}
@Provides

View File

@@ -31,12 +31,8 @@ import com.google.inject.name.Named;
import java.nio.file.Path;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
public final class ProxyCommonModule extends CommonModule {
public ProxyCommonModule(Path dataDirectory) {
@@ -46,7 +42,15 @@ public final class ProxyCommonModule extends CommonModule {
@Override
protected void configure() {
super.configure();
bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class);
bind(ProxyFloodgateApi.class).in(Singleton.class);
}
@Provides
@Singleton
public ProxyFloodgateConfig proxyFloodgateConfig(FloodgateConfig config) {
return (ProxyFloodgateConfig) config;
}
@Provides
@@ -55,14 +59,4 @@ public final class ProxyCommonModule extends CommonModule {
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return ProxyFloodgateConfig.class;
}
@Provides
@Singleton
public ProxyFloodgateApi proxyFloodgateApi(
PluginMessageManager pluginMessageManager,
FloodgateConfigHolder configHolder,
FloodgateLogger logger,
FloodgateCipher cipher) {
return new ProxyFloodgateApi(pluginMessageManager, configHolder, logger, cipher);
}
}

View File

@@ -30,29 +30,23 @@ import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.nio.file.Path;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
public final class ServerCommonModule extends CommonModule {
public ServerCommonModule(Path dataDirectory) {
super(dataDirectory);
}
@Override
protected void configure() {
super.configure();
bind(SimpleFloodgateApi.class).in(Singleton.class);
}
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return FloodgateConfig.class;
}
@Provides
@Singleton
public SimpleFloodgateApi floodgateApi(
PluginMessageManager pluginMessageManager,
FloodgateConfigHolder configHolder,
FloodgateLogger logger) {
return new SimpleFloodgateApi(pluginMessageManager, configHolder, logger);
}
}

View File

@@ -27,42 +27,50 @@ package org.geysermc.floodgate.news;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.news.data.AnnouncementData;
import org.geysermc.floodgate.news.data.BuildSpecificData;
import org.geysermc.floodgate.news.data.CheckAfterData;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.util.AutoBind;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.util.HttpClient;
import org.geysermc.floodgate.util.HttpClient.HttpResponse;
@AutoBind
public class NewsChecker {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
private final CommandUtil commandUtil;
private final FloodgateLogger logger;
private final Map<Integer, NewsItem> activeNewsItems = new HashMap<>();
private final String branch;
private final int build;
@Inject
@Named("commonScheduledPool")
private ScheduledExecutorService executorService;
@Inject
private CommandUtil commandUtil;
@Inject
private HttpClient httpClient;
@Inject
private FloodgateLogger logger;
@Inject
@Named("gitBranch")
private String branch;
@Inject
@Named("buildNumber")
private int build;
private boolean firstCheck;
public NewsChecker(CommandUtil commandUtil, FloodgateLogger logger, String branch, int build) {
this.commandUtil = commandUtil;
this.logger = logger;
this.branch = branch;
this.build = build;
}
@Inject
public void start() {
executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES);
}
@@ -72,10 +80,10 @@ public class NewsChecker {
}
private void checkNews() {
HttpResponse<JsonArray> response =
HttpUtils.getSilent(
Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME,
JsonArray.class);
HttpResponse<JsonArray> response = httpClient.getSilent(
Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME,
JsonArray.class
);
JsonArray array = response.getResponse();
@@ -193,8 +201,4 @@ public class NewsChecker {
handleNewsItem(null, item, action);
}
}
public void shutdown() {
executorService.shutdown();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.platform.command;
import cloud.commandframework.context.CommandContext;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.player.UserAudience;
public abstract class FloodgateSubCommand {
public abstract String name();
public abstract String description();
public abstract Permission permission();
public abstract void execute(CommandContext<UserAudience> context);
}

View File

@@ -23,33 +23,32 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate;
package org.geysermc.floodgate.platform.command;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public final class SpigotPlatform extends FloodgatePlatform {
@Inject private JavaPlugin plugin;
public abstract class SubCommands {
private final Set<Class<? extends FloodgateSubCommand>> toRegister = new HashSet<>();
private Set<FloodgateSubCommand> subCommands;
public void defineSubCommand(Class<? extends FloodgateSubCommand> subCommandClass) {
toRegister.add(subCommandClass);
}
@Inject
public SpigotPlatform(FloodgateApi api, PlatformInjector platformInjector,
FloodgateLogger logger, Injector injector) {
super(api, platformInjector, logger, injector);
public void registerSubCommands(Injector injector) {
Set<FloodgateSubCommand> subCommandSet = new HashSet<>();
for (Class<? extends FloodgateSubCommand> subCommand : toRegister) {
subCommandSet.add(injector.getInstance(subCommand));
}
subCommands = Collections.unmodifiableSet(subCommandSet);
}
@Override
public boolean enable(Module... postInitializeModules) {
boolean success = super.enable(postInitializeModules);
if (!success) {
Bukkit.getPluginManager().disablePlugin(plugin);
return false;
}
return true;
protected Set<FloodgateSubCommand> subCommands() {
return subCommands;
}
}

View File

@@ -48,11 +48,13 @@ import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.InvalidFormatException;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.Utils;
@@ -60,27 +62,30 @@ public final class FloodgateHandshakeHandler {
private final HandshakeHandlersImpl handshakeHandlers;
private final SimpleFloodgateApi api;
private final FloodgateCipher cipher;
private final FloodgateConfigHolder configHolder;
private final FloodgateConfig config;
private final SkinUploadManager skinUploadManager;
private final AttributeKey<FloodgatePlayer> playerAttribute;
private final FloodgateLogger logger;
private final LanguageManager languageManager;
public FloodgateHandshakeHandler(
HandshakeHandlersImpl handshakeHandlers,
SimpleFloodgateApi api,
FloodgateCipher cipher,
FloodgateConfigHolder configHolder,
FloodgateConfig config,
SkinUploadManager skinUploadManager,
AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger) {
FloodgateLogger logger,
LanguageManager languageManager) {
this.handshakeHandlers = handshakeHandlers;
this.api = api;
this.cipher = cipher;
this.configHolder = configHolder;
this.config = config;
this.skinUploadManager = skinUploadManager;
this.playerAttribute = playerAttribute;
this.logger = logger;
this.languageManager = languageManager;
}
/**
@@ -134,7 +139,7 @@ public final class FloodgateHandshakeHandler {
);
} catch (Exception e) {
// all the other exceptions are caused by invalid/tempered Floodgate data
if (configHolder.get().isDebug()) {
if (config.isDebug()) {
e.printStackTrace();
}
@@ -207,11 +212,16 @@ public final class FloodgateHandshakeHandler {
try {
HandshakeData handshakeData = new HandshakeDataImpl(
channel, true, bedrockData.clone(), configHolder.get(),
channel, true, bedrockData.clone(), config,
linkedPlayer != null ? linkedPlayer.clone() : null, hostname);
if (configHolder.get().getPlayerLink().isRequireLink() && linkedPlayer == null) {
handshakeData.setDisconnectReason("floodgate.core.not_linked");
if (config.getPlayerLink().isRequireLink() && linkedPlayer == null) {
String reason = languageManager.getString(
"floodgate.core.not_linked",
bedrockData.getLanguageCode(),
Constants.LINK_INFO_URL
);
handshakeData.setDisconnectReason(reason);
}
handshakeHandlers.callHandshakeHandlers(handshakeData);
@@ -245,7 +255,7 @@ public final class FloodgateHandshakeHandler {
String hostname) {
HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null,
bedrockData, configHolder.get(), null, hostname);
bedrockData, config, null, hostname);
handshakeHandlers.callHandshakeHandlers(handshakeData);
return new HandshakeResult(resultType, handshakeData, bedrockData, null);

View File

@@ -32,7 +32,6 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -72,7 +71,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private Map<String, PropertyKey> stringToPropertyKey;
static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) {
FloodgateApi api = InstanceHolder.getApi();
FloodgateApi api = FloodgateApi.getInstance();
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());

View File

@@ -36,14 +36,12 @@ public interface PluginMessageChannel {
Result handleProxyCall(
byte[] data,
UUID targetUuid,
String targetUsername,
Identity targetIdentity,
UUID sourceUuid,
String sourceUsername,
Identity sourceIdentity);
Identity sourceIdentity
);
Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername);
Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername);
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)

View File

@@ -28,6 +28,7 @@ package org.geysermc.floodgate.pluginmessage.channel;
import com.google.common.base.Charsets;
import com.google.inject.Inject;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@@ -41,7 +42,8 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageChannel;
public class FormChannel implements PluginMessageChannel {
private final FormDefinitions formDefinitions = FormDefinitions.instance();
private final Short2ObjectMap<Form> storedForms = new Short2ObjectOpenHashMap<>();
private final Short2ObjectMap<Form> storedForms =
Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>());
private final AtomicInteger nextFormId = new AtomicInteger(0);
@Inject private PluginMessageUtils pluginMessageUtils;
@@ -56,13 +58,10 @@ public class FormChannel implements PluginMessageChannel {
@Override
public Result handleProxyCall(
byte[] data,
UUID targetUuid,
String targetUsername,
Identity targetIdentity,
UUID sourceUuid,
String sourceUsername,
Identity sourceIdentity) {
Identity sourceIdentity
) {
if (sourceIdentity == Identity.SERVER) {
// send it to the client
return Result.forward();
@@ -89,7 +88,7 @@ public class FormChannel implements PluginMessageChannel {
}
@Override
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) {
callResponseConsumer(data);
return Result.handled();
}

View File

@@ -40,23 +40,26 @@ public final class PacketChannel implements PluginMessageChannel {
}
@Override
public Result handleProxyCall(byte[] data, UUID targetUuid, String targetUsername,
Identity targetIdentity, UUID sourceUuid, String sourceUsername,
Identity sourceIdentity) {
public Result handleProxyCall(
byte[] data,
UUID sourceUuid,
String sourceUsername,
Identity sourceIdentity
) {
if (sourceIdentity == Identity.SERVER) {
// send it to the client
return Result.forward();
}
if (sourceIdentity == Identity.PLAYER) {
return handleServerCall(data, targetUuid, targetUsername);
return handleServerCall(data, sourceUuid, sourceUsername);
}
return Result.handled();
}
@Override
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) {
return Result.kick("Cannot send packets from Geyser/Floodgate to Floodgate");
}

View File

@@ -25,7 +25,6 @@
package org.geysermc.floodgate.pluginmessage.channel;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@@ -36,7 +35,7 @@ import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannel;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData;
import org.geysermc.floodgate.skin.SkinDataImpl;
public class SkinChannel implements PluginMessageChannel {
@Inject private FloodgateApi api;
@@ -51,16 +50,13 @@ public class SkinChannel implements PluginMessageChannel {
@Override
public Result handleProxyCall(
byte[] data,
UUID targetUuid,
String targetUsername,
Identity targetIdentity,
UUID sourceUuid,
String sourceUsername,
Identity sourceIdentity) {
Identity sourceIdentity
) {
// we can only get skins from Geyser (client)
if (sourceIdentity == Identity.PLAYER) {
Result result = handleServerCall(data, targetUuid, targetUsername);
Result result = handleServerCall(data, sourceUuid, sourceUsername);
// aka translate 'handled' into 'forward' when send-floodgate-data is enabled
if (!result.isAllowed() && result.getReason() == null) {
if (config.isProxy() && ((ProxyFloodgateConfig) config).isSendFloodgateData()) {
@@ -78,8 +74,8 @@ public class SkinChannel implements PluginMessageChannel {
}
@Override
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
FloodgatePlayer floodgatePlayer = api.getPlayer(targetUuid);
public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) {
FloodgatePlayer floodgatePlayer = api.getPlayer(playerUuid);
if (floodgatePlayer == null) {
return Result.kick("Player sent skins data for a non-Floodgate player");
}
@@ -92,18 +88,10 @@ public class SkinChannel implements PluginMessageChannel {
return Result.kick("Got invalid skin data");
}
if (floodgatePlayer.isLinked()) {
return Result.handled();
}
String value = split[0];
String signature = split[1];
JsonObject result = new JsonObject();
result.addProperty("value", value);
result.addProperty("signature", signature);
SkinData skinData = new SkinData(value, signature);
SkinDataImpl skinData = new SkinDataImpl(value, signature);
floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, skinData);
skinApplier.applySkin(floodgatePlayer, skinData);

View File

@@ -42,27 +42,24 @@ public class TransferChannel implements PluginMessageChannel {
@Override
public Result handleProxyCall(
byte[] data,
UUID targetUuid,
String targetUsername,
Identity targetIdentity,
UUID sourceUuid,
String sourceUsername,
Identity sourceIdentity) {
Identity sourceIdentity
) {
if (sourceIdentity == Identity.SERVER) {
// send it to the client
return Result.forward();
}
if (sourceIdentity == Identity.PLAYER) {
handleServerCall(data, targetUuid, targetUsername);
handleServerCall(data, sourceUuid, sourceUsername);
}
return Result.handled();
}
@Override
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) {
return Result.kick("I'm sorry, I'm unable to transfer a server :(");
}

View File

@@ -25,8 +25,16 @@
package org.geysermc.floodgate.skin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public interface SkinApplier {
void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData);
/**
* Apply a skin to a {@link FloodgatePlayer player}
*
* @param floodgatePlayer player to apply skin to
* @param skinData data for skin to apply to player
*/
void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.skin;
import com.google.gson.JsonObject;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
public class SkinDataImpl implements SkinData {
private final String value;
private final String signature;
public SkinDataImpl(@NonNull String value, @NonNull String signature) {
this.value = Objects.requireNonNull(value);
this.signature = Objects.requireNonNull(signature);
}
public static SkinData from(JsonObject data) {
return new SkinDataImpl(
data.get("value").getAsString(),
data.get("signature").getAsString()
);
}
@Override
public @NonNull String value() {
return value;
}
@Override
public @NonNull String signature() {
return signature;
}
}

View File

@@ -25,21 +25,26 @@
package org.geysermc.floodgate.skin;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
@AllArgsConstructor
@Listener
@Singleton
public final class SkinUploadManager {
private final Int2ObjectMap<SkinUploadSocket> connections =
Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
private final FloodgateApi api;
private final SkinApplier applier;
private final FloodgateLogger logger;
@Inject private FloodgateApi api;
@Inject private SkinApplier applier;
@Inject private FloodgateLogger logger;
public void addConnectionIfNeeded(int id, String verifyCode) {
connections.computeIfAbsent(id, (ignored) -> {
@@ -53,4 +58,16 @@ public final class SkinUploadManager {
public void removeConnection(int id, SkinUploadSocket socket) {
connections.remove(id, socket);
}
public void closeAllSockets() {
for (SkinUploadSocket socket : connections.values()) {
socket.close();
}
connections.clear();
}
@Subscribe
public void onShutdown(ShutdownEvent ignored) {
closeAllSockets();
}
}

View File

@@ -35,6 +35,7 @@ import java.net.URI;
import javax.net.ssl.SSLException;
import lombok.Getter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
@@ -61,8 +62,8 @@ final class SkinUploadSocket extends WebSocketClient {
SkinUploadManager uploadManager,
FloodgateApi api,
SkinApplier applier,
FloodgateLogger logger) {
FloodgateLogger logger
) {
super(getWebsocketUri(id, verifyCode));
this.id = id;
this.verifyCode = verifyCode;
@@ -83,7 +84,7 @@ final class SkinUploadSocket extends WebSocketClient {
}
@Override
public void onOpen(ServerHandshake handshakedata) {
public void onOpen(ServerHandshake ignored) {
setConnectionLostTimeout(11);
}
@@ -114,10 +115,14 @@ final class SkinUploadSocket extends WebSocketClient {
player.getCorrectUsername());
return;
}
SkinData skinData = SkinDataImpl.from(message.getAsJsonObject("data"));
applier.applySkin(player, skinData);
// legacy stuff,
// will be removed shortly after or during the Floodgate-Geyser integration
if (!player.isLinked()) {
SkinData skinData = SkinData.from(message.getAsJsonObject("data"));
player.addProperty(PropertyKey.SKIN_UPLOADED, skinData);
applier.applySkin(player, skinData);
}
}
break;

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Automatically binds an instance of itself as an eager singleton during the post-initialise stage
* of the FloodgatePlatform. Add the annotation to a class, and it should automatically create an
* instance and inject it for you.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoBind {
}

View File

@@ -27,13 +27,15 @@ package org.geysermc.floodgate.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -42,29 +44,36 @@ import org.checkerframework.checker.nullness.qual.Nullable;
// resources are properly closed and ignoring the original stack trace is intended
@SuppressWarnings({"PMD.CloseResource", "PMD.PreserveStackTrace"})
public class HttpUtils {
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
private static final Gson GSON = new Gson();
@Singleton
public class HttpClient {
private static final String USER_AGENT = "GeyserMC/Floodgate";
public static CompletableFuture<DefaultHttpResponse> asyncGet(String urlString) {
return CompletableFuture.supplyAsync(() -> get(urlString), EXECUTOR_SERVICE);
private final Gson gson = new Gson();
@Inject
@Named("commonPool")
private ExecutorService executorService;
public CompletableFuture<DefaultHttpResponse> asyncGet(String urlString) {
return CompletableFuture.supplyAsync(() -> get(urlString), executorService);
}
public static DefaultHttpResponse get(String urlString) {
public <T> CompletableFuture<HttpResponse<T>> asyncGet(String urlString, Class<T> response) {
return CompletableFuture.supplyAsync(() -> get(urlString, response), executorService);
}
public DefaultHttpResponse get(String urlString) {
return readDefaultResponse(request(urlString));
}
public static <T> HttpResponse<T> get(String urlString, Class<T> clazz) {
public <T> HttpResponse<T> get(String urlString, Class<T> clazz) {
return readResponse(request(urlString), clazz);
}
public static <T> HttpResponse<T> getSilent(String urlString, Class<T> clazz) {
public <T> HttpResponse<T> getSilent(String urlString, Class<T> clazz) {
return readResponseSilent(request(urlString), clazz);
}
private static HttpURLConnection request(String urlString) {
private HttpURLConnection request(String urlString) {
HttpURLConnection connection;
try {
@@ -88,18 +97,20 @@ public class HttpUtils {
}
@NonNull
private static <T> HttpResponse<T> readResponse(HttpURLConnection connection, Class<T> clazz) {
private <T> HttpResponse<T> readResponse(HttpURLConnection connection, Class<T> clazz) {
InputStreamReader streamReader = createReader(connection);
if (streamReader == null) {
return new HttpResponse<>(-1, null);
}
int responseCode = -1;
try {
int responseCode = connection.getResponseCode();
T response = GSON.fromJson(streamReader, clazz);
responseCode = connection.getResponseCode();
T response = gson.fromJson(streamReader, clazz);
return new HttpResponse<>(responseCode, response);
} catch (Exception ignored) {
return new HttpResponse<>(-1, null);
// e.g. when the response isn't JSON
return new HttpResponse<>(responseCode, null);
} finally {
try {
streamReader.close();
@@ -109,9 +120,7 @@ public class HttpUtils {
}
@NonNull
private static <T> HttpResponse<T> readResponseSilent(
HttpURLConnection connection,
Class<T> clazz) {
private <T> HttpResponse<T> readResponseSilent(HttpURLConnection connection, Class<T> clazz) {
try {
return readResponse(connection, clazz);
} catch (Exception ignored) {
@@ -120,7 +129,7 @@ public class HttpUtils {
}
@NonNull
private static DefaultHttpResponse readDefaultResponse(HttpURLConnection connection) {
private DefaultHttpResponse readDefaultResponse(HttpURLConnection connection) {
InputStreamReader streamReader = createReader(connection);
if (streamReader == null) {
return new DefaultHttpResponse(-1, null);
@@ -128,7 +137,7 @@ public class HttpUtils {
try {
int responseCode = connection.getResponseCode();
JsonObject response = GSON.fromJson(streamReader, JsonObject.class);
JsonObject response = gson.fromJson(streamReader, JsonObject.class);
return new DefaultHttpResponse(responseCode, response);
} catch (Exception exception) {
throw new RuntimeException("Failed to read response", exception);
@@ -141,16 +150,12 @@ public class HttpUtils {
}
@Nullable
private static InputStreamReader createReader(HttpURLConnection connection) {
private InputStreamReader createReader(HttpURLConnection connection) {
InputStream stream;
try {
stream = connection.getInputStream();
} catch (Exception exception) {
try {
stream = connection.getErrorStream();
} catch (Exception exception1) {
throw new RuntimeException("Both the input and the error stream failed?!");
}
stream = connection.getErrorStream();
}
// it's null for example when it couldn't connect to the server

View File

@@ -26,30 +26,27 @@
package org.geysermc.floodgate.util;
import com.google.common.base.Joiner;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
/**
* Manages translations for strings in Floodgate
*/
@RequiredArgsConstructor
@Singleton
public final class LanguageManager {
private final Map<String, Properties> localeMappings = new HashMap<>();
private final FloodgateConfigHolder configHolder;
private final FloodgateLogger logger;
@Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
/**
* The locale used in console and as a fallback
@@ -71,24 +68,15 @@ public final class LanguageManager {
}
}
public boolean isLoaded() {
return logger != null && defaultLocale != null;
}
/**
* Tries to load the log's locale file once a string has been requested
*/
@Inject
private void init() {
if (!loadLocale("en_US")) {// Fallback
logger.error("Failed to load the fallback language. This will likely cause errors!");
}
FloodgateConfig config = configHolder.get();
if (config == null) {
// :thonk:
return;
}
defaultLocale = formatLocale(config.getDefaultLocale());
if (isValidLanguage(defaultLocale)) {
@@ -125,21 +113,11 @@ public final class LanguageManager {
return true;
}
InputStream localeStream = LanguageManager.class.getClassLoader().getResourceAsStream(
"languages/texts/" + formatLocale + ".properties");
Properties properties =
Utils.readProperties("languages/texts/" + formatLocale + ".properties");
// load the locale
if (localeStream != null) {
Properties localeProp = new Properties();
try (Reader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) {
localeProp.load(reader);
} catch (Exception e) {
throw new AssertionError("Failed to load Floodgate locale", e);
}
// insert the locale into the mappings
localeMappings.put(formatLocale, localeProp);
if (properties != null) {
localeMappings.put(formatLocale, properties);
return true;
}
@@ -167,14 +145,6 @@ public final class LanguageManager {
* @return translated string or "key arg1, arg2 (etc.)" if it was not found in the given locale
*/
public String getString(String key, String locale, Object... values) {
if (!isLoaded()) {
init();
// we can skip everything if the LanguageManager can't be loaded yet
if (!isLoaded()) {
return formatNotFound(key, values);
}
}
Properties properties = localeMappings.get(locale);
String formatString = null;

View File

@@ -26,24 +26,29 @@
package org.geysermc.floodgate.util;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Named;
import org.bstats.MetricsBase;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bstats.json.JsonObjectBuilder;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.MetricsConfig;
import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.platform.util.PlatformUtils;
@Listener
@AutoBind
public final class Metrics {
private final MetricsBase metricsBase;
@@ -144,4 +149,9 @@ public final class Metrics {
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
@Subscribe
public void onShutdown(ShutdownEvent ignored) {
metricsBase.shutdown();
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.util;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.event.lifecycle.PostEnableEvent;
@AutoBind
@Listener
public final class PostEnableMessages {
private final List<String> messages = new ArrayList<>();
@Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
@Inject
@Named("commonScheduledPool")
private ScheduledExecutorService executorService;
public void add(String[] message, Object... args) {
StringBuilder builder = new StringBuilder();
builder.append("\n**********************************\n");
for (String part : message) {
builder.append("* ").append(part).append('\n');
}
builder.append("**********************************");
messages.add(MessageFormatter.format(builder.toString(), args));
}
@Inject
private void init() {
registerPrefixMessages();
}
private void registerPrefixMessages() {
String prefix = config.getRawUsernamePrefix();
if (prefix.isEmpty()) {
add(new String[]{
"You specified an empty prefix in your Floodgate config for Bedrock players!",
"Should a Java player join and a Bedrock player join with the same username, unwanted results and conflicts will happen!",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
});
} else if (!Utils.isUniquePrefix(prefix)) {
add(new String[]{
"The prefix you entered in your Floodgate config ({}) could lead to username conflicts!",
"Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
}, prefix, prefix, prefix, prefix);
}
if (prefix.length() >= 16) {
add(new String[]{
"The prefix you entered in your Floodgate config ({}) is longer than a Java username can be!",
"Because of this, we reset the prefix to the default Floodgate prefix (.)"
}, prefix);
} else if (prefix.length() > 2) {
// we only have to warn them if we haven't replaced the prefix
add(new String[]{
"The prefix you entered in your Floodgate config ({}) is long! ({} characters)",
"A prefix is there to prevent username conflicts. However, a long prefix makes the chance of username conflicts higher.",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
}, prefix, prefix.length());
}
}
@Subscribe
public void onPostEnable(PostEnableEvent ignored) {
// normally proxies don't have a lot of plugins, so proxies don't need to sleep as long
executorService.schedule(
() -> messages.forEach(logger::warn),
config.isProxy() ? 2 : 5,
TimeUnit.SECONDS
);
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2019-2022 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/Floodgate
*/
package org.geysermc.floodgate.util;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
public final class PrefixCheckTask {
public static void checkAndExecuteDelayed(FloodgateConfig config, FloodgateLogger logger) {
if (Utils.isUniquePrefix(config.getUsernamePrefix())) {
return;
}
new Thread(() -> {
// normally proxies don't have a lot of plugins, so proxies don't need to sleep as long
try {
Thread.sleep(config.isProxy() ? 1000 : 2000);
} catch (InterruptedException ignored) {
}
if (config.getUsernamePrefix().isEmpty()) {
logger.warn("\n" +
"**********************************\n" +
"* You specified an empty prefix in your Floodgate config for Bedrock players!\n" +
"* Should a Java player join and a Bedrock player join with the same username, unwanted results and conflicts will happen!\n" +
"* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" +
"**********************************");
return;
}
logger.warn(
"\n" +
"**********************************\n" +
"* The prefix you entered in your Floodgate config ({}) could lead to username conflicts!\n" +
"* Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!\n" +
"* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" +
"**********************************",
config.getUsernamePrefix(), config.getUsernamePrefix(),
config.getUsernamePrefix(), config.getUsernamePrefix());
}).start();
}
}

View File

@@ -287,7 +287,7 @@ public final class ReflectionUtils {
}
/**
* Get the value of a field and cast it to <T>.
* Get the value of a field and cast it to T.
*
* @param instance the instance to get the value from
* @param field the field to get the value from
@@ -301,7 +301,7 @@ public final class ReflectionUtils {
}
/**
* Get the value of a field and cast it to <T>.
* Get the value of a field and cast it to T.
*
* @param instance the instance to get the value from
* @param fieldName the field to get the value from
@@ -424,7 +424,7 @@ public final class ReflectionUtils {
}
@Nullable
public static Method getMethod(
public static Method getMethodThatReturns(
Class<?> clazz,
Class<?> returnType,
boolean declared,

View File

@@ -33,21 +33,19 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.lang.annotation.Annotation;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Utils {
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^[a-zA-Z0-9_]{0,16}$");
private static final Pattern NON_UNIQUE_PREFIX = Pattern.compile("^\\w{0,16}$");
private static final Pattern DATABASE_NAME = Pattern.compile(Constants.DATABASE_NAME_FORMAT);
/**
@@ -66,33 +64,21 @@ public class Utils {
}
}
public static List<String> readAllLines(String resourcePath) throws IOException {
InputStream stream = Utils.class.getClassLoader().getResourceAsStream(resourcePath);
try (BufferedReader reader = newBufferedReader(stream, StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>();
for (; ; ) {
String line = reader.readLine();
if (line == null) {
break;
}
result.add(line);
}
return result;
}
}
public static BufferedReader newBufferedReader(InputStream inputStream, Charset charset) {
CharsetDecoder decoder = charset.newDecoder();
Reader reader = new InputStreamReader(inputStream, decoder);
return new BufferedReader(reader);
}
/**
* Reads a properties resource file
* @param resourceFile the resource file to read
* @return the properties file if the resource exists, otherwise null
* @throws AssertionError if something went wrong while readin the resource file
*/
public static Properties readProperties(String resourceFile) {
Properties properties = new Properties();
try (InputStream is = Utils.class.getClassLoader().getResourceAsStream(resourceFile)) {
properties.load(is);
if (is == null) {
return null;
}
properties.load(new InputStreamReader(is, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
throw new AssertionError("Failed to read properties file", e);
}
return properties;
}
@@ -151,4 +137,42 @@ public class Utils {
future.completeExceptionally(ex);
return future;
}
/**
* Returns a set of all the classes that are annotated by a given annotation.
* Keep in mind that these are from a set of generated annotations generated
* at compile time by the annotation processor, meaning that arbitrary annotations
* cannot be passed into this method and expected to get a set of classes back.
*
* @param annotationClass the annotation class
* @return a set of all the classes annotated by the given annotation
*/
public static Set<Class<?>> getGeneratedClassesForAnnotation(Class<? extends Annotation> annotationClass) {
return getGeneratedClassesForAnnotation(annotationClass.getName());
}
/**
* Returns a set of all the classes that are annotated by a given annotation.
* Keep in mind that these are from a set of generated annotations generated
* at compile time by the annotation processor, meaning that arbitrary annotations
* cannot be passed into this method and expected to have a set of classes
* returned back.
*
* @param input the fully qualified name of the annotation
* @return a set of all the classes annotated by the given annotation
*/
public static Set<Class<?>> getGeneratedClassesForAnnotation(String input) {
try (InputStream annotatedClass = Utils.class.getClassLoader().getResourceAsStream(input);
BufferedReader reader = new BufferedReader(new InputStreamReader(annotatedClass))) {
return reader.lines().map(className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException ex) {
throw new RuntimeException("Failed to find class for annotation " + input, ex);
}
}).collect(Collectors.toSet());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -26,12 +26,12 @@
package org.geysermc.floodgate.util;
public final class Constants {
public static final String VERSION = "${floodgateVersion}";
public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}");
public static final String GIT_BRANCH = "${branch}";
public static final String VERSION = "@floodgateVersion@";
public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@");
public static final String GIT_BRANCH = "@branch@";
public static final int METRICS_ID = 14649;
public static final char COLOR_CHAR = '§';
public static final char COLOR_CHAR = '\u00A7';
public static final boolean DEBUG_MODE = false;
public static final boolean PRINT_ALL_PACKETS = false;
@@ -55,7 +55,7 @@ public final class Constants {
public static final String NTP_SERVER = "time.cloudflare.com";
public static final String INTERNAL_ERROR_MESSAGE =
"An internal error happened while handling Floodgate data." +
"An internal error happened while handling Floodgate data." +
" Try logging in again or contact a server administrator if the issue persists.";
public static final String UNSUPPORTED_DATA_VERSION =
"Received an unsupported Floodgate data version." +

View File

@@ -0,0 +1,4 @@
[*]
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 4

View File

@@ -1,10 +1,17 @@
val mariadbClientVersion = "2.7.4"
dependencies {
provided(projects.core)
implementation("org.mariadb.jdbc", "mariadb-java-client" , mariadbClientVersion)
provided(projects.core)
// update HikariCP when we move to Java 11+
implementation("com.zaxxer", "HikariCP", "4.0.3")
implementation("com.mysql", "mysql-connector-j", "8.0.32") {
exclude("com.google.protobuf", "protobuf-java")
}
}
description = "The Floodgate database extension for MySQL"
relocate("org.mariadb")
// relocate everything from mysql-connector-j and HikariCP
relocate("com.mysql")
relocate("com.zaxxer.hikari")
relocate("org.slf4j")

View File

@@ -25,6 +25,8 @@
package org.geysermc.floodgate.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
@@ -43,309 +45,296 @@ import org.geysermc.floodgate.database.config.MysqlConfig;
import org.geysermc.floodgate.link.CommonPlayerLink;
import org.geysermc.floodgate.link.LinkRequestImpl;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.mariadb.jdbc.MariaDbPoolDataSource;
public class MysqlDatabase extends CommonPlayerLink {
private MariaDbPoolDataSource pool;
private HikariDataSource dataSource;
@Override
public void load() {
getLogger().info("Connecting to a MySQL-like database...");
try {
Class.forName("org.mariadb.jdbc.Driver");
MysqlConfig databaseconfig = getConfig(MysqlConfig.class);
@Override
public void load() {
getLogger().info("Connecting to a MySQL-like database...");
try {
MysqlConfig config = getConfig(MysqlConfig.class);
pool = new MariaDbPoolDataSource();
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://" + config.getHostname() + "/" + config.getDatabase());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
hikariConfig.setPoolName("floodgate-linking-mysql");
hikariConfig.setMinimumIdle(5);
hikariConfig.setMaximumPoolSize(10);
String hostname = databaseconfig.getHostname();
if (hostname.contains(":")) {
String[] split = hostname.split(":");
dataSource = new HikariDataSource(hikariConfig);
pool.setServerName(split[0]);
try {
pool.setPortNumber(Integer.parseInt(split[1]));
} catch (NumberFormatException exception) {
getLogger().info("{} is not a valid port! Will use the default port", split[1]);
}
} else {
pool.setServerName(hostname);
}
pool.setUser(databaseconfig.getUsername());
pool.setPassword(databaseconfig.getPassword());
pool.setDatabaseName(databaseconfig.getDatabase());
pool.setMinPoolSize(2);
pool.setMaxPoolSize(10);
try (Connection connection = pool.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " +
"`bedrockId` BINARY(16) NOT NULL , " +
"`javaUniqueId` BINARY(16) NOT NULL , " +
"`javaUsername` VARCHAR(16) NOT NULL , " +
" PRIMARY KEY (`bedrockId`) , " +
" INDEX (`bedrockId`, `javaUniqueId`)" +
") ENGINE = InnoDB;"
);
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayersRequest` ( " +
"`javaUsername` VARCHAR(16) NOT NULL , `javaUniqueId` BINARY(16) NOT NULL , " +
"`linkCode` VARCHAR(16) NOT NULL , " +
"`bedrockUsername` VARCHAR(16) NOT NULL ," +
"`requestTime` BIGINT NOT NULL , " +
" PRIMARY KEY (`javaUsername`), INDEX(`requestTime`)" +
" ) ENGINE = InnoDB;"
);
}
}
getLogger().info("Connected to MySQL-like database.");
} catch (ClassNotFoundException exception) {
getLogger().error("The required class to load the MySQL database wasn't found");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
try (Connection connection = dataSource.getConnection()) {
try (Statement statement = connection.createStatement()) {
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " +
"`bedrockId` BINARY(16) NOT NULL , " +
"`javaUniqueId` BINARY(16) NOT NULL , " +
"`javaUsername` VARCHAR(16) NOT NULL , " +
" PRIMARY KEY (`bedrockId`) , " +
" INDEX (`bedrockId`, `javaUniqueId`)" +
") ENGINE = InnoDB;"
);
statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayersRequest` ( " +
"`javaUsername` VARCHAR(16) NOT NULL , `javaUniqueId` BINARY(16) NOT NULL , " +
"`linkCode` VARCHAR(16) NOT NULL , " +
"`bedrockUsername` VARCHAR(16) NOT NULL ," +
"`requestTime` BIGINT NOT NULL , " +
" PRIMARY KEY (`javaUsername`), INDEX(`requestTime`)" +
" ) ENGINE = InnoDB;"
);
}
}
getLogger().info("Connected to MySQL-like database.");
} catch (SQLException exception) {
getLogger().error("Error while loading database", exception);
}
}
@Override
public void stop() {
super.stop();
pool.close();
}
@Override
public void stop() {
super.stop();
dataSource.close();
}
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
try (ResultSet result = query.executeQuery()) {
if (!result.next()) {
return null;
}
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(result.getBytes("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
} catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
@Override
@NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
try (ResultSet result = query.executeQuery()) {
if (!result.next()) {
return null;
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(playerId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
try (ResultSet result = query.executeQuery()) {
return result.next();
}
}
} catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`javaUsername`=VALUES(`javaUsername`);"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
String javaUsername = result.getString("javaUsername");
UUID javaUniqueId = bytesToUUID(result.getBytes("javaUniqueId"));
return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId);
}
}
}
} catch (SQLException exception) {
getLogger().error("Error while getting LinkedPlayer", exception);
throw new CompletionException("Error while getting LinkedPlayer", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(javaId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`linkCode`=VALUES(`linkCode`), " +
"`bedrockUsername`=VALUES(`bedrockUsername`), " +
"`requestTime`=VALUES(`requestTime`);"
)) {
query.setString(1, javaUsername);
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, linkCode);
query.setString(4, bedrockUsername);
query.setLong(5, Instant.now().getEpochSecond());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
@Override
@NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(playerId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
try (ResultSet result = query.executeQuery()) {
return result.next();
}
}
}
} catch (SQLException exception) {
getLogger().error("Error while checking if player is a LinkedPlayer", exception);
throw new CompletionException(
"Error while checking if player is a LinkedPlayer", exception
);
}
}, getExecutorService());
}
private void removeLinkRequest(String javaUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
@Override
@NonNull
public CompletableFuture<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`javaUsername`=VALUES(`javaUsername`);"
)) {
query.setBytes(1, uuidToBytes(bedrockId));
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?"
)) {
byte[] uuidBytes = uuidToBytes(javaId);
query.setBytes(1, uuidBytes);
query.setBytes(2, uuidBytes);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while unlinking player", exception);
throw new CompletionException("Error while unlinking player", exception);
}
}, getExecutorService());
}
@Override
@NonNull
public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername
) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername
) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " +
"`linkCode`=VALUES(`linkCode`), " +
"`bedrockUsername`=VALUES(`bedrockUsername`), " +
"`requestTime`=VALUES(`requestTime`);"
)) {
query.setString(1, javaUsername);
query.setBytes(2, uuidToBytes(javaId));
query.setString(3, linkCode);
query.setString(4, bedrockUsername);
query.setLong(5, Instant.now().getEpochSecond());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while linking player", exception);
throw new CompletionException("Error while linking player", exception);
}
}
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
private void removeLinkRequest(String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up LinkRequest", exception);
}
}
private LinkRequest getLinkRequest0(String javaUsername) {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
@Override
@NonNull
public CompletableFuture<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code
) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
try (ResultSet result = query.executeQuery()) {
if (result.next()) {
UUID javaId = bytesToUUID(result.getBytes(2));
String linkCode = result.getString(3);
String bedrockUsername = result.getString(4);
long requestTime = result.getLong(5);
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
}
}
} catch (SQLException exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
private LinkRequest getLinkRequest0(String javaUsername) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) {
query.setString(1, javaUsername);
try (ResultSet result = query.executeQuery()) {
if (result.next()) {
UUID javaId = bytesToUUID(result.getBytes(2));
String linkCode = result.getString(3);
String bedrockUsername = result.getString(4);
long requestTime = result.getLong(5);
return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername,
requestTime);
}
}
return null;
}
} catch (SQLException exception) {
getLogger().error("Error while getLinkRequest", exception);
throw new CompletionException("Error while getLinkRequest", exception);
}
return null;
}
public void cleanLinkRequests() {
try (Connection connection = pool.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?"
)) {
query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up link requests", exception);
}
public void cleanLinkRequests() {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?"
)) {
query.setLong(1, Instant.now().getEpochSecond() - getVerifyLinkTimeout());
query.executeUpdate();
}
} catch (SQLException exception) {
getLogger().error("Error while cleaning up link requests", exception);
}
}
private byte[] uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return uuidBytes;
}
private UUID bytesToUUID(byte[] uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong());
}
private byte[] uuidToBytes(UUID uuid) {
byte[] uuidBytes = new byte[16];
ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return uuidBytes;
}
private UUID bytesToUUID(byte[] uuidBytes) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong());
}
}

View File

@@ -29,8 +29,8 @@ import lombok.Getter;
@Getter
public class MysqlConfig implements DatabaseConfig {
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
private String hostname = "localhost";
private String database = "floodgate";
private String username = "floodgate";
private String password;
}

View File

@@ -1,3 +1,5 @@
org.gradle.configureondemand=true
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.parallel=true
version=2.2.2-SNAPSHOT

View File

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

10
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright <EFBFBD> 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.
@@ -32,10 +32,10 @@
# 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».
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
#
# Important for patching:
#

View File

@@ -1,3 +1,4 @@
@file:Suppress("UnstableApiUsage")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
@@ -14,7 +15,19 @@ dependencyResolutionManagement {
}
// Paper, Velocity
maven("https://papermc.io/repo/repository/maven-public")
// maven("https://repo.papermc.io/repository/maven-releases") {
// mavenContent { releasesOnly() }
// }
// maven("https://repo.papermc.io/repository/maven-snapshots") {
// mavenContent { snapshotsOnly() }
// }
maven("https://repo.papermc.io/repository/maven-public") {
content {
includeGroupByRegex(
"(io\\.papermc\\..*|com\\.destroystokyo\\..*|com\\.velocitypowered)"
)
}
}
// Spigot
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
mavenContent { snapshotsOnly() }
@@ -43,7 +56,8 @@ pluginManagement {
gradlePluginPortal()
}
plugins {
id("net.kyori.blossom") version "1.2.0"
id("net.kyori.indra")
id("net.kyori.indra.git")
}
includeBuild("build-logic")
}
@@ -51,6 +65,7 @@ pluginManagement {
rootProject.name = "floodgate-parent"
include(":api")
include(":ap")
include(":core")
include(":bungee")
include(":spigot")

View File

@@ -27,6 +27,7 @@ package org.geysermc.floodgate;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
@@ -43,7 +44,7 @@ import org.geysermc.floodgate.util.SpigotProtocolSupportHandler;
import org.geysermc.floodgate.util.SpigotProtocolSupportListener;
public final class SpigotPlugin extends JavaPlugin {
private SpigotPlatform platform;
private FloodgatePlatform platform;
private Injector injector;
@Override
@@ -54,7 +55,7 @@ public final class SpigotPlugin extends JavaPlugin {
new SpigotPlatformModule(this)
);
platform = injector.getInstance(SpigotPlatform.class);
platform = injector.getInstance(FloodgatePlatform.class);
long endCtm = System.currentTimeMillis();
injector.getInstance(FloodgateLogger.class)
@@ -66,14 +67,18 @@ public final class SpigotPlugin extends JavaPlugin {
boolean usePaperListener = ReflectionUtils.getClassSilently(
"com.destroystokyo.paper.event.profile.PreFillProfileEvent") != null;
platform.enable(
new SpigotCommandModule(this),
new SpigotAddonModule(),
new PluginMessageModule(),
(usePaperListener ? new PaperListenerModule() : new SpigotListenerModule())
);
try {
platform.enable(
new SpigotCommandModule(this),
new SpigotAddonModule(),
new PluginMessageModule(),
(usePaperListener ? new PaperListenerModule() : new SpigotListenerModule())
);
} catch (Exception exception) {
Bukkit.getPluginManager().disablePlugin(this);
throw exception;
}
//todo add proper support for disabling things on shutdown and enabling this on enable
injector.getInstance(HandshakeHandlers.class)
.addHandshakeHandler(injector.getInstance(SpigotHandshakeHandler.class));

View File

@@ -25,25 +25,28 @@
package org.geysermc.floodgate.inject.spigot;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.util.ClassNames;
import org.geysermc.floodgate.util.ReflectionUtils;
@RequiredArgsConstructor
@Singleton
public final class SpigotInjector extends CommonPlatformInjector {
@Inject private FloodgateLogger logger;
private Object serverConnection;
private String injectedFieldName;
@@ -51,54 +54,56 @@ public final class SpigotInjector extends CommonPlatformInjector {
@Override
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public boolean inject() throws Exception {
public void inject() throws Exception {
if (isInjected()) {
return true;
return;
}
if (getServerConnection() != null) {
for (Field field : serverConnection.getClass().getDeclaredFields()) {
if (field.getType() == List.class) {
field.setAccessible(true);
Object serverConnection = getServerConnection();
if (serverConnection == null) {
throw new RuntimeException("Unable to find server connection");
}
ParameterizedType parameterType = ((ParameterizedType) field.getGenericType());
Type listType = parameterType.getActualTypeArguments()[0];
for (Field field : serverConnection.getClass().getDeclaredFields()) {
if (field.getType() == List.class) {
field.setAccessible(true);
// the list we search has ChannelFuture as type
if (listType != ChannelFuture.class) {
continue;
}
ParameterizedType parameterType = ((ParameterizedType) field.getGenericType());
Type listType = parameterType.getActualTypeArguments()[0];
injectedFieldName = field.getName();
List<?> newList = new CustomList((List<?>) field.get(serverConnection)) {
@Override
public void onAdd(Object object) {
try {
injectClient((ChannelFuture) object);
} catch (Exception exception) {
exception.printStackTrace();
}
}
};
// inject existing
synchronized (newList) {
for (Object object : newList) {
try {
injectClient((ChannelFuture) object);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
field.set(serverConnection, newList);
injected = true;
return true;
// the list we search has ChannelFuture as type
if (listType != ChannelFuture.class) {
continue;
}
injectedFieldName = field.getName();
List<?> newList = new CustomList((List<?>) field.get(serverConnection)) {
@Override
public void onAdd(Object object) {
try {
injectClient((ChannelFuture) object);
} catch (Exception exception) {
exception.printStackTrace();
}
}
};
// inject existing
synchronized (newList) {
for (Object object : newList) {
try {
injectClient((ChannelFuture) object);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
field.set(serverConnection, newList);
injected = true;
return;
}
}
return false;
}
public void injectClient(ChannelFuture future) {
@@ -120,36 +125,48 @@ public final class SpigotInjector extends CommonPlatformInjector {
}
@Override
public boolean removeInjection() throws Exception {
public void removeInjection() {
if (!isInjected()) {
return true;
return;
}
// remove injection from clients
for (Channel channel : getInjectedClients()) {
removeAddonsCall(channel);
}
getInjectedClients().clear();
// and change the list back to the original
// let's change the list back to the original first
// so that new connections are not handled through our custom list
Object serverConnection = getServerConnection();
if (serverConnection != null) {
Field field = ReflectionUtils.getField(serverConnection.getClass(), injectedFieldName);
List<?> list = (List<?>) ReflectionUtils.getValue(serverConnection, field);
Object value = ReflectionUtils.getValue(serverConnection, field);
if (list instanceof CustomList) {
CustomList customList = (CustomList) list;
if (value instanceof CustomList) {
// all we have to do is replace the list with the original list.
// the original list is up-to-date, so we don't have to clear/add/whatever anything
CustomList customList = (CustomList) value;
ReflectionUtils.setValue(serverConnection, field, customList.getOriginalList());
customList.clear();
customList.addAll(list);
return;
}
// we could replace all references of CustomList that are directly in 'value', but that
// only brings you so far. ProtocolLib for example stores the original value
// (which would be our CustomList e.g.) in a separate object
logger.debug(
"Unable to remove all references of Floodgate due to {}! ",
value.getClass().getName()
);
}
// remove injection from clients
for (Channel channel : injectedClients()) {
removeAddonsCall(channel);
}
//todo make sure that all references are removed from the channels,
// except from one AttributeKey with Floodgate player data which could be used
// after reloading
injected = false;
return true;
}
public Object getServerConnection() throws IllegalAccessException, InvocationTargetException {
private Object getServerConnection() {
if (serverConnection != null) {
return serverConnection;
}
@@ -158,14 +175,11 @@ public final class SpigotInjector extends CommonPlatformInjector {
// method by CraftBukkit to get the instance of the MinecraftServer
Object minecraftServerInstance = ReflectionUtils.invokeStatic(minecraftServer, "getServer");
for (Method method : minecraftServer.getDeclaredMethods()) {
if (ClassNames.SERVER_CONNECTION.equals(method.getReturnType())) {
// making sure that it's a getter
if (method.getParameterTypes().length == 0) {
serverConnection = method.invoke(minecraftServerInstance);
}
}
}
Method method = ReflectionUtils.getMethodThatReturns(
minecraftServer, ClassNames.SERVER_CONNECTION, true
);
serverConnection = ReflectionUtils.invoke(minecraftServerInstance, method);
return serverConnection;
}

View File

@@ -29,6 +29,8 @@ import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
@@ -59,7 +61,12 @@ public final class SpigotPlatformModule extends AbstractModule {
@Override
protected void configure() {
bind(SpigotPlugin.class).toInstance(plugin);
bind(PlatformUtils.class).to(SpigotPlatformUtils.class);
bind(CommonPlatformInjector.class).to(SpigotInjector.class);
bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger());
bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class);
bind(SkinApplier.class).to(SpigotSkinApplier.class);
}
@Provides
@@ -68,12 +75,6 @@ public final class SpigotPlatformModule extends AbstractModule {
return plugin;
}
@Provides
@Singleton
public FloodgateLogger floodgateLogger(LanguageManager languageManager) {
return new JavaUtilFloodgateLogger(plugin.getLogger(), languageManager);
}
/*
Commands / Listeners
*/
@@ -98,12 +99,6 @@ public final class SpigotPlatformModule extends AbstractModule {
DebugAddon / PlatformInjector
*/
@Provides
@Singleton
public CommonPlatformInjector platformInjector() {
return new SpigotInjector();
}
@Provides
@Named("packetEncoder")
public String packetEncoder() {
@@ -144,12 +139,6 @@ public final class SpigotPlatformModule extends AbstractModule {
return new SpigotPluginMessageRegistration(plugin);
}
@Provides
@Singleton
public SkinApplier skinApplier(SpigotVersionSpecificMethods versionSpecificMethods) {
return new SpigotSkinApplier(versionSpecificMethods, plugin);
}
@Provides
@Singleton
public SpigotVersionSpecificMethods versionSpecificMethods() {

View File

@@ -25,32 +25,34 @@
package org.geysermc.floodgate.pluginmessage;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.SpigotPlugin;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.skin.SkinApplyEventImpl;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData;
import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.ClassNames;
import org.geysermc.floodgate.util.ReflectionUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
@Singleton
public final class SpigotSkinApplier implements SkinApplier {
private final SpigotVersionSpecificMethods versionSpecificMethods;
private final SpigotPlugin plugin;
public SpigotSkinApplier(
SpigotVersionSpecificMethods versionSpecificMethods,
SpigotPlugin plugin) {
this.versionSpecificMethods = versionSpecificMethods;
this.plugin = plugin;
}
@Inject private SpigotVersionSpecificMethods versionSpecificMethods;
@Inject private SpigotPlugin plugin;
@Inject private EventBus eventBus;
@Override
public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) {
public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) {
applySkin0(floodgatePlayer, skinData, true);
}
@@ -60,9 +62,11 @@ public final class SpigotSkinApplier implements SkinApplier {
// player is probably not logged in yet
if (player == null) {
if (firstTry) {
Bukkit.getScheduler().runTaskLater(plugin,
Bukkit.getScheduler().runTaskLater(
plugin,
() -> applySkin0(floodgatePlayer, skinData, false),
10 * 1000);
10 * 20
);
}
return;
}
@@ -73,11 +77,22 @@ public final class SpigotSkinApplier implements SkinApplier {
throw new IllegalStateException("The GameProfile cannot be null! " + player.getName());
}
// Need to be careful here - getProperties() returns an authlib PropertyMap, which extends
// MultiMap from Guava. Floodgate relocates Guava.
PropertyMap properties = profile.getProperties();
properties.removeAll("textures");
Property property = new Property("textures", skinData.getValue(), skinData.getSignature());
properties.put("textures", property);
SkinData currentSkin = currentSkin(properties);
SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData);
event.setCancelled(floodgatePlayer.isLinked());
eventBus.fire(event);
if (event.isCancelled()) {
return;
}
replaceSkin(properties, event.newSkin());
// By running as a task, we don't run into async issues
plugin.getServer().getScheduler().runTask(plugin, () -> {
@@ -89,4 +104,19 @@ public final class SpigotSkinApplier implements SkinApplier {
}
});
}
private SkinData currentSkin(PropertyMap properties) {
for (Property texture : properties.get("textures")) {
if (!texture.getValue().isEmpty()) {
return new SkinDataImpl(texture.getValue(), texture.getSignature());
}
}
return null;
}
private void replaceSkin(PropertyMap properties, SkinData skinData) {
properties.removeAll("textures");
Property property = new Property("textures", skinData.value(), skinData.signature());
properties.put("textures", property);
}
}

View File

@@ -1,4 +1,4 @@
var velocityVersion = "3.0.1"
var velocityVersion = "3.1.1"
var log4jVersion = "2.11.2"
var gsonVersion = "2.8.8"
var guavaVersion = "25.1-jre"

View File

@@ -29,6 +29,7 @@ import com.google.inject.Inject;
import com.google.inject.Injector;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import java.nio.file.Path;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
@@ -69,4 +70,9 @@ public final class VelocityPlugin {
new PluginMessageModule()
);
}
@Subscribe
public void onProxyShutdown(ProxyShutdownEvent event) {
platform.disable();
}
}

View File

@@ -36,21 +36,19 @@ import io.netty.channel.ChannelInitializer;
import java.lang.reflect.Method;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.inject.CommonPlatformInjector;
@RequiredArgsConstructor
public final class VelocityInjector extends CommonPlatformInjector {
private final ProxyServer server;
private final FloodgateLogger logger;
@Getter private boolean injected;
@Override
@SuppressWarnings("rawtypes")
public boolean inject() {
public void inject() {
if (isInjected()) {
return true;
return;
}
Object connectionManager = getValue(server, "cm");
@@ -72,7 +70,8 @@ public final class VelocityInjector extends CommonPlatformInjector {
Method backendSetter = getMethod(backendInitializerHolder, "set", ChannelInitializer.class);
invoke(backendInitializerHolder, backendSetter,
new VelocityChannelInitializer(this, backendInitializer, true));
return injected = true;
injected = true;
}
@Override
@@ -81,9 +80,9 @@ public final class VelocityInjector extends CommonPlatformInjector {
}
@Override
public boolean removeInjection() {
logger.error("Floodgate cannot remove itself from Velocity without a reboot");
return false;
public void removeInjection() {
throw new IllegalStateException(
"Floodgate cannot remove itself from Velocity without a reboot");
}
@RequiredArgsConstructor

View File

@@ -27,17 +27,27 @@ package org.geysermc.floodgate.logger;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import lombok.RequiredArgsConstructor;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.LanguageManager;
import org.slf4j.Logger;
@RequiredArgsConstructor
@Singleton
public final class Slf4jFloodgateLogger implements FloodgateLogger {
private final Logger logger;
private final LanguageManager languageManager;
@Inject private Logger logger;
private LanguageManager languageManager;
@Inject
private void init(LanguageManager languageManager, FloodgateConfig config) {
this.languageManager = languageManager;
if (config.isDebug() && !logger.isDebugEnabled()) {
Configurator.setLevel(logger.getName(), Level.DEBUG);
}
}
@Override
public void error(String message, Object... args) {
@@ -74,20 +84,6 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger {
logger.trace(message, args);
}
@Override
public void enableDebug() {
if (!logger.isDebugEnabled()) {
Configurator.setLevel(logger.getName(), Level.DEBUG);
}
}
@Override
public void disableDebug() {
if (logger.isDebugEnabled()) {
Configurator.setLevel(logger.getName(), Level.INFO);
}
}
@Override
public boolean isDebug() {
return logger.isDebugEnabled();

Some files were not shown because too many files have changed in this diff Show More