diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..016d0bfd --- /dev/null +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/.gitignore b/.gitignore index 58ae9e78..27c8ac2c 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/Jenkinsfile b/Jenkinsfile index b52cca93..58099d91 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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)] - } - } - } } } \ No newline at end of file diff --git a/ap/build.gradle.kts b/ap/build.gradle.kts new file mode 100644 index 00000000..e69de29b diff --git a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfigHolder.java b/ap/src/main/java/org/geysermc/floodgate/ap/AutoBindProcessor.java similarity index 62% rename from core/src/main/java/org/geysermc/floodgate/config/FloodgateConfigHolder.java rename to ap/src/main/java/org/geysermc/floodgate/ap/AutoBindProcessor.java index a4fd63b4..52bd93d2 100644 --- a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfigHolder.java +++ b/ap/src/main/java/org/geysermc/floodgate/ap/AutoBindProcessor.java @@ -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 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"); } } diff --git a/ap/src/main/java/org/geysermc/floodgate/ap/ClassProcessor.java b/ap/src/main/java/org/geysermc/floodgate/ap/ClassProcessor.java new file mode 100644 index 00000000..3758711a --- /dev/null +++ b/ap/src/main/java/org/geysermc/floodgate/ap/ClassProcessor.java @@ -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 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 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 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 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()); + } +} diff --git a/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000..53b2e379 --- /dev/null +++ b/ap/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.geysermc.floodgate.ap.AutoBindProcessor \ No newline at end of file diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 0bd75df1..09fb2163 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -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) } diff --git a/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java index 0d2b0efd..999935ab 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java +++ b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java @@ -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 getGamertagFor(long xuid); + default FloodgateEventBus getEventBus() { + return InstanceHolder.getEventBus(); + } + /** * Returns the instance that manages all the linking. */ diff --git a/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java index d0c6af24..159310ee 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java +++ b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java @@ -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 castApi(Class cast) { - return (T) api; - } } diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java new file mode 100644 index 00000000..935cde21 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java @@ -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> { +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java new file mode 100644 index 00000000..0d9ab473 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java @@ -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 extends Subscriber { +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java b/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java new file mode 100644 index 00000000..7a4b16c1 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java @@ -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(); + } +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeData.java b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeData.java index 44eb1c3c..1ebc6525 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeData.java +++ b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeData.java @@ -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. diff --git a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandler.java b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandler.java index 2aca5498..8ce06253 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandler.java +++ b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandler.java @@ -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 { /** diff --git a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandlers.java b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandlers.java index ef19b545..088ac9af 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandlers.java +++ b/api/src/main/java/org/geysermc/floodgate/api/handshake/HandshakeHandlers.java @@ -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). + *
+ * 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 diff --git a/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java b/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java index 5fe765e5..4c28ac40 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java +++ b/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java @@ -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. diff --git a/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequestResult.java b/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequestResult.java index ea7c795c..3d8fad03 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequestResult.java +++ b/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequestResult.java @@ -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. */ diff --git a/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java b/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java index d1d533a8..e3afee90 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java +++ b/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java @@ -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(); } diff --git a/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java b/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java index c69578c5..6205da0d 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java +++ b/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java @@ -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 getProperty(PropertyKey key); + @Deprecated T getProperty(String key); + @Deprecated T removeProperty(PropertyKey key); + @Deprecated T removeProperty(String key); + @Deprecated T addProperty(PropertyKey key, Object value); + @Deprecated T addProperty(String key, Object value); /** diff --git a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java index 04e1d4d0..62bebaf8 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java +++ b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java @@ -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 diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 64ac14db..6200d2c9 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -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 { diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index d6dbe684..b501b9e2 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -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" diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 54158d84..cbb5e8a8 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -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().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().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>>() diff --git a/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts index 0dba1832..f1d9ab8f 100644 --- a/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts @@ -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() } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate.generate-templates.gradle.kts b/build-logic/src/main/kotlin/floodgate.generate-templates.gradle.kts new file mode 100644 index 00000000..5f89b302 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate.generate-templates.gradle.kts @@ -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().all { + val javaDestination = layout.buildDirectory.dir("generated/sources/templates/$name") + val javaSrcDir = layout.projectDirectory.dir("src/$name/templates") + val javaGenerateTask = tasks.register( + getTaskName("template", "sources") + ) { + filteringCharset = Charsets.UTF_8.name() + from(javaSrcDir) + into(javaDestination) + filter("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( + getTaskName("template", "resources") + ) { + filteringCharset = Charsets.UTF_8.name() + from(resourcesSrcDir) + into(resourcesDestination) + filter("tokens" to replacements()) + } + resources.srcDir(resourcesGenerateTask.map { it.outputs }) + } + + return configureIdeSync( + tasks.register("allTemplateSources") { + dependsOn(tasks.withType()) + }, + tasks.register("allTemplateResources") { + dependsOn(tasks.withType()) + } + ) +} + +fun Project.configureIdeSync(vararg generateAllTasks: TaskProvider) { + extensions.findByType { + synchronizationTasks(generateAllTasks) + } + + extensions.findByType { + if (project != null) { + (project as ExtensionAware).extensions.configure { + (this as ExtensionAware).extensions.configure { + afterSync(generateAllTasks) + } + } + } + } + + //todo wasn't able to find something for VS(Code) +} + +inline fun 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() + + fun replaceToken(key: String, value: () -> Any) { + replaceToken(key, value.invoke()) + } + + fun replaceToken(key: String, value: Any) { + replacements[key] = value.toString() + } + + fun replacements(): Map { + return replacements + } +} + +open class GenerateResourceTemplates : GenerateAnyTemplates() +open class GenerateSourceTemplates : GenerateAnyTemplates() diff --git a/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts index 60c3e272..0f7b7618 100644 --- a/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts @@ -1,34 +1,15 @@ plugins { id("floodgate.shadow-conventions") - id("com.jfrog.artifactory") - id("maven-publish") + id("net.kyori.indra.publishing") } -publishing { - publications { - create("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") } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts index bf3de089..7f342d8d 100644 --- a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts @@ -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) diff --git a/build.gradle.kts b/build.gradle.kts index b64f35aa..da89fe19 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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" } diff --git a/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java index 7181edd2..e6a3e964 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java +++ b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java @@ -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) { diff --git a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java index e62a1776..9723cf58 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java @@ -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("", "")); } } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java index cbe9b5e5..4943d88c 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java @@ -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 */ diff --git a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java index 921ddf23..178657b0 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java +++ b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java @@ -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()); diff --git a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java index 5c0dce97..ce6105ec 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java +++ b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java @@ -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 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]); } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 02fec9dd..46b43de2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -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 { - 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()) + } } diff --git a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java index 7b963d73..70487093 100644 --- a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java @@ -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() { diff --git a/core/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java index a3336e09..ede56730 100644 --- a/core/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java @@ -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 { diff --git a/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java index 94db6743..5ef49e46 100644 --- a/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java @@ -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 players = new HashMap<>(); + private final Map players = new ConcurrentHashMap<>(); private final Cache 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 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(); diff --git a/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java b/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java index 45287f9e..b87a20ee 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java @@ -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); diff --git a/core/src/main/java/org/geysermc/floodgate/command/main/FirewallCheckSubcommand.java b/core/src/main/java/org/geysermc/floodgate/command/main/FirewallCheckSubcommand.java index 66f439b7..6fa989f5 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/main/FirewallCheckSubcommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/main/FirewallCheckSubcommand.java @@ -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 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 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 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> executeChecks( - BooleanSupplier... checks) { - + private CompletableFuture> executeChecks(BooleanSupplier... checks) { return CompletableFuture.supplyAsync(() -> { AtomicInteger okCount = new AtomicInteger(); AtomicInteger failCount = new AtomicInteger(); diff --git a/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java b/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java index 093289fe..446e8106 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java @@ -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 buildCommand(CommandManager commandManager) { Builder 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 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> executor; - } } diff --git a/core/src/main/java/org/geysermc/floodgate/command/main/VersionSubcommand.java b/core/src/main/java/org/geysermc/floodgate/command/main/VersionSubcommand.java new file mode 100644 index 00000000..ea985778 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/command/main/VersionSubcommand.java @@ -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 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!"); + }); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java b/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java index 90f897fb..87c2ec52 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java +++ b/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java @@ -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), diff --git a/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java b/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java index 43cbfb3e..e4311058 100644 --- a/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java +++ b/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java @@ -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 configClass; private final KeyProducer keyProducer; private final FloodgateCipher cipher; - private final FloodgateLogger logger; - @SuppressWarnings("unchecked") public 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); } } } diff --git a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java b/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java index b7b6b866..4a87b0d4 100644 --- a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java +++ b/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java @@ -52,7 +52,9 @@ public class FloodgateConfig implements GenericPostInitializeCallback= 16) { + usernamePrefix = "."; + } + return CallbackResult.ok(); } diff --git a/core/src/main/java/org/geysermc/floodgate/event/EventBus.java b/core/src/main/java/org/geysermc/floodgate/event/EventBus.java new file mode 100644 index 00000000..c8e6f9bc --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/EventBus.java @@ -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> + implements FloodgateEventBus { + @Override + protected > B makeSubscription( + @NonNull Class eventClass, + @NonNull Subscribe subscribe, + @NonNull H listener, + @NonNull BiConsumer handler + ) { + return (B) new EventSubscriber<>( + eventClass, subscribe.postOrder(), subscribe.ignoreCancelled(), listener, handler + ); + } + + @Override + protected > B makeSubscription( + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder + ) { + return (B) new EventSubscriber<>(eventClass, handler, postOrder); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java b/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java new file mode 100644 index 00000000..f01c209b --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java @@ -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 extends SubscriberImpl implements FloodgateSubscriber { + EventSubscriber( + @NonNull Class eventClass, + @NonNull Consumer handler, + @NonNull PostOrder postOrder + ) { + super(eventClass, handler, postOrder); + } + + EventSubscriber( + Class eventClass, + PostOrder postOrder, + boolean ignoreCancelled, + H handlerInstance, + BiConsumer handler + ) { + super(eventClass, postOrder, ignoreCancelled, handlerInstance, handler); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java new file mode 100644 index 00000000..f274ff16 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java @@ -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 { +} diff --git a/core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java new file mode 100644 index 00000000..30e782c5 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java @@ -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 { +} diff --git a/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java b/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java new file mode 100644 index 00000000..52148986 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java @@ -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; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/event/util/ListenerAnnotationMatcher.java b/core/src/main/java/org/geysermc/floodgate/event/util/ListenerAnnotationMatcher.java new file mode 100644 index 00000000..e0881e23 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/util/ListenerAnnotationMatcher.java @@ -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> { + @Override + public boolean matches(TypeLiteral typeLiteral) { + Class rawType = typeLiteral.getRawType(); + return rawType.isAnnotationPresent(Listener.class); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java b/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java index 9025ca0d..1547a2be 100644 --- a/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java +++ b/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java @@ -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 injectedClients = new HashSet<>(); + private final Set injectedClients = + Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); private final Map, InjectorAddon> addons = new HashMap<>(); @@ -49,6 +48,10 @@ public abstract class CommonPlatformInjector implements PlatformInjector { return injectedClients.remove(channel); } + public Set injectedClients() { + return injectedClients; + } + @Override public boolean addAddon(InjectorAddon addon) { return addons.putIfAbsent(addon.getClass(), addon) == null; diff --git a/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java b/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java index 0973b8d9..9324a097 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java +++ b/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java @@ -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(); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java b/core/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java index 1ef1227e..bc57c61b 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java +++ b/core/src/main/java/org/geysermc/floodgate/link/GlobalPlayerLinking.java @@ -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( diff --git a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java similarity index 74% rename from core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java rename to core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java index b8febe29..a91ba679 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java +++ b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java @@ -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 files; - try { - files = Files.list(dataDirectory) - .filter(path -> Files.isRegularFile(path) && path.toString().endsWith("jar")) + try (Stream 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 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(); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/logger/JavaUtilFloodgateLogger.java b/core/src/main/java/org/geysermc/floodgate/logger/JavaUtilFloodgateLogger.java index f3151ef1..97f1af3f 100644 --- a/core/src/main/java/org/geysermc/floodgate/logger/JavaUtilFloodgateLogger.java +++ b/core/src/main/java/org/geysermc/floodgate/logger/JavaUtilFloodgateLogger.java @@ -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; diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinData.java b/core/src/main/java/org/geysermc/floodgate/module/AutoBindModule.java similarity index 66% rename from core/src/main/java/org/geysermc/floodgate/skin/SkinData.java rename to core/src/main/java/org/geysermc/floodgate/module/AutoBindModule.java index 84162710..1c2933de 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinData.java +++ b/core/src/main/java/org/geysermc/floodgate/module/AutoBindModule.java @@ -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); } } diff --git a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java index f075bc22..881f3ede 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -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 void hear(TypeLiteral type, TypeEncounter encounter) { + encounter.register((InjectionListener) 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 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 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 diff --git a/core/src/main/java/org/geysermc/floodgate/module/ProxyCommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/ProxyCommonModule.java index bae8cf22..77d52d2c 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/ProxyCommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/ProxyCommonModule.java @@ -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 floodgateConfigClass() { return ProxyFloodgateConfig.class; } - - @Provides - @Singleton - public ProxyFloodgateApi proxyFloodgateApi( - PluginMessageManager pluginMessageManager, - FloodgateConfigHolder configHolder, - FloodgateLogger logger, - FloodgateCipher cipher) { - return new ProxyFloodgateApi(pluginMessageManager, configHolder, logger, cipher); - } } diff --git a/core/src/main/java/org/geysermc/floodgate/module/ServerCommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/ServerCommonModule.java index b1b4ab5a..23514824 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/ServerCommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/ServerCommonModule.java @@ -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 floodgateConfigClass() { return FloodgateConfig.class; } - - @Provides - @Singleton - public SimpleFloodgateApi floodgateApi( - PluginMessageManager pluginMessageManager, - FloodgateConfigHolder configHolder, - FloodgateLogger logger) { - return new SimpleFloodgateApi(pluginMessageManager, configHolder, logger); - } } diff --git a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java index 901321c3..40358247 100644 --- a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java +++ b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java @@ -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 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 response = - HttpUtils.getSilent( - Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME, - JsonArray.class); + HttpResponse 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(); - } } diff --git a/core/src/main/java/org/geysermc/floodgate/platform/command/FloodgateSubCommand.java b/core/src/main/java/org/geysermc/floodgate/platform/command/FloodgateSubCommand.java new file mode 100644 index 00000000..25515ddc --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/platform/command/FloodgateSubCommand.java @@ -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 context); +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java b/core/src/main/java/org/geysermc/floodgate/platform/command/SubCommands.java similarity index 60% rename from spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java rename to core/src/main/java/org/geysermc/floodgate/platform/command/SubCommands.java index c3ba5abf..75ece406 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/platform/command/SubCommands.java @@ -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> toRegister = new HashSet<>(); + private Set subCommands; + + public void defineSubCommand(Class 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 subCommandSet = new HashSet<>(); + for (Class 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 subCommands() { + return subCommands; } } diff --git a/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java b/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java index 950d999c..b84ff1ba 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java @@ -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 playerAttribute; private final FloodgateLogger logger; + private final LanguageManager languageManager; public FloodgateHandshakeHandler( HandshakeHandlersImpl handshakeHandlers, SimpleFloodgateApi api, FloodgateCipher cipher, - FloodgateConfigHolder configHolder, + FloodgateConfig config, SkinUploadManager skinUploadManager, AttributeKey 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); diff --git a/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java b/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java index acfcab75..5945f66e 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java +++ b/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java @@ -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 stringToPropertyKey; static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) { - FloodgateApi api = InstanceHolder.getApi(); + FloodgateApi api = FloodgateApi.getInstance(); UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java index 28f8c068..067edd31 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java @@ -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) diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java index 9d48b732..1da89bc1 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java @@ -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
storedForms = new Short2ObjectOpenHashMap<>(); + private final Short2ObjectMap 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(); } diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java index a6da65c5..2296f35e 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java @@ -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"); } diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java index 2e82eceb..8e9e64b0 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java @@ -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); diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java index 94e9982e..7ca754ed 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java @@ -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 :("); } diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java index 0386b723..c8e7684c 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java @@ -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); } diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java new file mode 100644 index 00000000..9f44af79 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java @@ -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; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java index a6c49395..61db1ab6 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java @@ -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 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(); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java index 87c04459..5018f8fa 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java @@ -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; diff --git a/core/src/main/java/org/geysermc/floodgate/util/AutoBind.java b/core/src/main/java/org/geysermc/floodgate/util/AutoBind.java new file mode 100644 index 00000000..6c1eec2e --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/util/AutoBind.java @@ -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 { +} diff --git a/core/src/main/java/org/geysermc/floodgate/util/HttpUtils.java b/core/src/main/java/org/geysermc/floodgate/util/HttpClient.java similarity index 76% rename from core/src/main/java/org/geysermc/floodgate/util/HttpUtils.java rename to core/src/main/java/org/geysermc/floodgate/util/HttpClient.java index 56ef2540..960dc1b1 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/HttpUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/HttpClient.java @@ -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 asyncGet(String urlString) { - return CompletableFuture.supplyAsync(() -> get(urlString), EXECUTOR_SERVICE); + private final Gson gson = new Gson(); + @Inject + @Named("commonPool") + private ExecutorService executorService; + + public CompletableFuture asyncGet(String urlString) { + return CompletableFuture.supplyAsync(() -> get(urlString), executorService); } - public static DefaultHttpResponse get(String urlString) { + public CompletableFuture> asyncGet(String urlString, Class response) { + return CompletableFuture.supplyAsync(() -> get(urlString, response), executorService); + } + + public DefaultHttpResponse get(String urlString) { return readDefaultResponse(request(urlString)); } - public static HttpResponse get(String urlString, Class clazz) { + public HttpResponse get(String urlString, Class clazz) { return readResponse(request(urlString), clazz); } - public static HttpResponse getSilent(String urlString, Class clazz) { + public HttpResponse getSilent(String urlString, Class 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 HttpResponse readResponse(HttpURLConnection connection, Class clazz) { + private HttpResponse readResponse(HttpURLConnection connection, Class 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 HttpResponse readResponseSilent( - HttpURLConnection connection, - Class clazz) { + private HttpResponse readResponseSilent(HttpURLConnection connection, Class 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 diff --git a/core/src/main/java/org/geysermc/floodgate/util/LanguageManager.java b/core/src/main/java/org/geysermc/floodgate/util/LanguageManager.java index 92c9a395..a3d659bf 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/LanguageManager.java +++ b/core/src/main/java/org/geysermc/floodgate/util/LanguageManager.java @@ -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 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; diff --git a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java index 008d215e..7e388940 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java @@ -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(); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java b/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java new file mode 100644 index 00000000..e526c470 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java @@ -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 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 + ); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java b/core/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java deleted file mode 100644 index 4631171f..00000000 --- a/core/src/main/java/org/geysermc/floodgate/util/PrefixCheckTask.java +++ /dev/null @@ -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(); - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java index 80352d87..709dfd43 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java @@ -287,7 +287,7 @@ public final class ReflectionUtils { } /** - * Get the value of a field and cast it to . + * 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 . + * 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, diff --git a/core/src/main/java/org/geysermc/floodgate/util/Utils.java b/core/src/main/java/org/geysermc/floodgate/util/Utils.java index d6efda3a..19ef78de 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Utils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/Utils.java @@ -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 readAllLines(String resourcePath) throws IOException { - InputStream stream = Utils.class.getClassLoader().getResourceAsStream(resourcePath); - try (BufferedReader reader = newBufferedReader(stream, StandardCharsets.UTF_8)) { - List 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> getGeneratedClassesForAnnotation(Class 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> 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); + } + } } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 38cb4a52..204f4fe4 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 38cb4a52df713cb0bc1738370aa8135c01f0cabc +Subproject commit 204f4fe4920defac3a472e762d95233d0756f35f diff --git a/core/src/main/java/org/geysermc/floodgate/util/Constants.java b/core/src/main/templates/org/geysermc/floodgate/util/Constants.java similarity index 92% rename from core/src/main/java/org/geysermc/floodgate/util/Constants.java rename to core/src/main/templates/org/geysermc/floodgate/util/Constants.java index 5e97aba3..b79a610f 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Constants.java +++ b/core/src/main/templates/org/geysermc/floodgate/util/Constants.java @@ -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." + diff --git a/database/mysql/.editorconfig b/database/mysql/.editorconfig new file mode 100644 index 00000000..1f0feacf --- /dev/null +++ b/database/mysql/.editorconfig @@ -0,0 +1,4 @@ +[*] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 \ No newline at end of file diff --git a/database/mysql/build.gradle.kts b/database/mysql/build.gradle.kts index cc59658b..3975a2e0 100644 --- a/database/mysql/build.gradle.kts +++ b/database/mysql/build.gradle.kts @@ -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") diff --git a/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java b/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java index 16797c6b..60c19716 100644 --- a/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java +++ b/database/mysql/src/main/java/org/geysermc/floodgate/database/MysqlDatabase.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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()); + } } diff --git a/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java b/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java index 0a2e0c26..b85748e7 100644 --- a/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java +++ b/database/mysql/src/main/java/org/geysermc/floodgate/database/config/MysqlConfig.java @@ -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; } diff --git a/gradle.properties b/gradle.properties index af7d8325..af961a84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.parallel=true \ No newline at end of file +org.gradle.parallel=true + +version=2.2.2-SNAPSHOT \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897..070cb702 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew index 1b6c7873..c53aefaa 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -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 «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # diff --git a/settings.gradle.kts b/settings.gradle.kts index 7df9d09c..30992cbc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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") diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java index 6254cc17..2a5af4ee 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java +++ b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java @@ -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)); diff --git a/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java index 9ea99b1a..17812624 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java +++ b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java @@ -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; } diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java index 5547046b..0649280e 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java @@ -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() { diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java index 86f64c96..fa599f40 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java @@ -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); + } } diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index 2ddcfae5..5cd7064a 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -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" diff --git a/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java b/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java index 259d29a7..d7c16754 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java +++ b/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java @@ -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(); + } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java index 1a4d43b1..4d122cf1 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java +++ b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java @@ -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 diff --git a/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jFloodgateLogger.java b/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jFloodgateLogger.java index 3b5dd543..17efe8a6 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jFloodgateLogger.java +++ b/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jFloodgateLogger.java @@ -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(); diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java index 8678af5e..fb412b47 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java @@ -56,11 +56,9 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageUtils; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.VelocityCommandUtil; import org.geysermc.floodgate.util.VelocityPlatformUtils; import org.geysermc.floodgate.util.VelocitySkinApplier; -import org.slf4j.Logger; @RequiredArgsConstructor public final class VelocityPlatformModule extends AbstractModule { @@ -70,6 +68,8 @@ public final class VelocityPlatformModule extends AbstractModule { protected void configure() { bind(CommandUtil.class).to(VelocityCommandUtil.class); bind(PlatformUtils.class).to(VelocityPlatformUtils.class); + bind(FloodgateLogger.class).to(Slf4jFloodgateLogger.class); + bind(SkinApplier.class).to(VelocitySkinApplier.class); } @Provides @@ -89,12 +89,6 @@ public final class VelocityPlatformModule extends AbstractModule { return commandManager; } - @Provides - @Singleton - public FloodgateLogger floodgateLogger(Logger logger, LanguageManager languageManager) { - return new Slf4jFloodgateLogger(logger, languageManager); - } - /* Commands / Listeners */ @@ -119,20 +113,14 @@ public final class VelocityPlatformModule extends AbstractModule { return new VelocityPluginMessageRegistration(proxy); } - @Provides - @Singleton - public SkinApplier skinApplier(ProxyServer server) { - return new VelocitySkinApplier(server); - } - /* DebugAddon / PlatformInjector */ @Provides @Singleton - public CommonPlatformInjector platformInjector(ProxyServer server, FloodgateLogger logger) { - return new VelocityInjector(server, logger); + public CommonPlatformInjector platformInjector(ProxyServer server) { + return new VelocityInjector(server); } @Provides diff --git a/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java b/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java index 482e5712..f6a8b75f 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java +++ b/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java @@ -33,7 +33,6 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; -import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import java.util.UUID; @@ -64,21 +63,6 @@ public class VelocityPluginMessageUtils extends PluginMessageUtils { return; } - UUID targetUuid = null; - String targetUsername = null; - Identity targetIdentity = Identity.UNKNOWN; - - ChannelMessageSink target = event.getTarget(); - if (target instanceof Player) { - Player player = (Player) target; - targetUuid = player.getUniqueId(); - targetUsername = player.getUsername(); - targetIdentity = Identity.PLAYER; - - } else if (target instanceof ServerConnection) { - targetIdentity = Identity.SERVER; - } - UUID sourceUuid = null; String sourceUsername = null; Identity sourceIdentity = Identity.UNKNOWN; @@ -94,8 +78,9 @@ public class VelocityPluginMessageUtils extends PluginMessageUtils { 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.setResult(result.isAllowed() ? ForwardResult.forward() : ForwardResult.handled()); diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java index 2d925725..f9818cbc 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java @@ -25,25 +25,60 @@ package org.geysermc.floodgate.util; +import com.google.inject.Inject; +import com.google.inject.Singleton; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.util.GameProfile.Property; import java.util.ArrayList; import java.util.List; -import lombok.RequiredArgsConstructor; +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.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; -@RequiredArgsConstructor +@Singleton public class VelocitySkinApplier implements SkinApplier { - private final ProxyServer server; + @Inject private ProxyServer server; + @Inject private EventBus eventBus; @Override - public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { + public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> { List properties = new ArrayList<>(player.getGameProfileProperties()); - properties.add(new Property("textures", skinData.getValue(), skinData.getSignature())); + + 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()); player.setGameProfileProperties(properties); }); } + + private SkinData currentSkin(List properties) { + for (Property property : properties) { + if (property.getName().equals("textures")) { + if (!property.getValue().isEmpty()) { + return new SkinDataImpl(property.getValue(), property.getSignature()); + } + } + } + return null; + } + + private void replaceSkin(List properties, SkinData skinData) { + properties.removeIf(property -> property.getName().equals("textures")); + properties.add(new Property("textures", skinData.value(), skinData.signature())); + } }