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

Merge pull request #397 from GeyserMC/development

* Started using plugin messages internally. Started subcommand refactor

* Removed config holder and changed a few things

* Small changes to platforms and injectors

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

* Deprecate handshake handlers and player properties

* Added auto-binding

* Switched event library

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

* Simplified plugin message channel logic

* Switched to Hikari for MySQL

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

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

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

* remove the use of Streams and Optionals

* correct delay in SpigotSkinApplier to use ticks instead of milliseconds

* Minor changes

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

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

* News needs a scheduled executor

* Bumped version to 2.1.1 and publish entire java component

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

* Close all skin sockets on shutdown (#363)

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

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

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

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

* Fixed building Floodgate and added a version subcommand

* Remove Blossom and use templates

* Use weak references for injected Netty channels

* Use newSetFromMap

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

* Use a common ScheduledThreadPool and make the player map concurrent

* Don't try to remove all injector references

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

And updated Gradle

* Remove Jenkinsfile

* Retrieve version from gradle.properties

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

* Add publishing to downloads site (#385)

* Add publishing to downloads site

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

---------

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

* Notify Discord after building & fixed building

* Temporarily restore Jenkinsfile to give people time to switch

* Localize floodgate.core.not_linked (#383)

* Localize floodgate.core.not_linked

* Update languages submodule

* not_linked string has changed slightly

---------

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

* Update bstats dependency

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

* Only attempt publishing if running in GeyserMC/Floodgate

* Also restrict the discord notification

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

* Made various changes to the build action

* Update maven deploy location

* Relocate MySQL database extension dependencies

* Shutdown metrics on platform shutdown (#386)

* Shutdown metrics on platorm shutdown

* Listen to event instead of hardcoding it

* Annotate Metrics as a Listener

* Use temporary bStats fork to properly shutdown bStats

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

* Formatting change

---------

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

---------

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

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

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

97
.gitignore vendored
View File

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

82
Jenkinsfile vendored
View File

@@ -26,87 +26,5 @@ pipeline {
} }
} }
} }
stage ('Deploy') {
when {
anyOf {
branch "master"
branch "development"
}
}
steps {
rtGradleDeployer(
id: "GRADLE_DEPLOYER",
serverId: "opencollab-artifactory",
releaseRepo: "maven-releases",
snapshotRepo: "maven-snapshots"
)
rtGradleResolver(
id: "GRADLE_RESOLVER",
serverId: "opencollab-artifactory"
)
rtGradleRun(
usesPlugin: true,
tool: 'Gradle 7',
rootDir: "",
useWrapper: true,
buildFile: 'build.gradle.kts',
tasks: 'artifactoryPublish',
deployerId: "GRADLE_DEPLOYER",
resolverId: "GRADLE_RESOLVER"
)
rtPublishBuildInfo(
serverId: "opencollab-artifactory"
)
}
}
}
post {
always {
script {
def changeLogSets = currentBuild.changeSets
def message = "**Changes:**"
if (changeLogSets.size() == 0) {
message += "\n*No changes.*"
} else {
def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "")
def count = 0;
def extra = 0;
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
if (count <= 10) {
def entry = entries[j]
def commitId = entry.commitId.substring(0, 6)
message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}"
count++
} else {
extra++;
}
}
}
if (extra != 0) {
message += "\n - ${extra} more commits"
}
}
env.changes = message
}
deleteDir()
withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) {
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Floodgate)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
success {
script {
if (env.BRANCH_NAME == 'master') {
build propagate: false, wait: false, job: 'GeyserMC/Floodgate-Fabric/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
}
}
}
} }
} }

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

View File

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

View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.ap;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/*
* Copied from Geyser
*/
public class ClassProcessor extends AbstractProcessor {
private final String annotationClassName;
private Path outputPath;
private final Set<String> locations = new HashSet<>();
public ClassProcessor(String annotationClassName) {
this.annotationClassName = annotationClassName;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
processingEnv.getMessager().printMessage(
Kind.NOTE, "Initializing processor " + annotationClassName
);
String outputFile = processingEnv.getOptions().get("metadataOutputFile");
if (outputFile != null && !outputFile.isEmpty()) {
outputPath = Paths.get(outputFile);
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
if (!roundEnv.errorRaised()) {
complete();
}
return false;
}
if (!contains(annotations, annotationClassName)) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() != ElementKind.CLASS) {
continue;
}
if (!contains(element.getAnnotationMirrors(), annotationClassName)) {
continue;
}
TypeElement typeElement = (TypeElement) element;
locations.add(typeElement.getQualifiedName().toString());
}
return false;
}
public boolean contains(Collection<? extends TypeElement> elements, String className) {
if (elements.isEmpty()) {
return false;
}
for (TypeElement element : elements) {
if (element.getQualifiedName().contentEquals(className)) {
return true;
}
}
return false;
}
public boolean contains(List<? extends AnnotationMirror> elements, String className) {
if (elements.isEmpty()) {
return false;
}
for (AnnotationMirror element : elements) {
if (element.getAnnotationType().toString().equals(className)) {
return true;
}
}
return false;
}
public void complete() {
// Read existing annotation list and verify each class still has this annotation
try (BufferedReader reader = createReader()) {
if (reader != null) {
reader.lines().forEach(canonicalName -> {
if (!locations.contains(canonicalName)) {
TypeElement element =
processingEnv.getElementUtils().getTypeElement(canonicalName);
if (element != null && element.getKind() == ElementKind.CLASS &&
contains(element.getAnnotationMirrors(), annotationClassName)) {
locations.add(canonicalName);
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
if (!locations.isEmpty()) {
try (BufferedWriter writer = createWriter()) {
for (String location : locations) {
writer.write(location);
writer.newLine();
}
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
processingEnv.getMessager().printMessage(Kind.NOTE,
"Did not find any classes annotated with " + annotationClassName
);
}
processingEnv.getMessager().printMessage(
Kind.NOTE, "Completed processing for " + annotationClassName
);
}
private BufferedReader createReader() throws IOException {
if (outputPath != null) {
processingEnv.getMessager().printMessage(Kind.NOTE,
"Reading existing " + annotationClassName + " list from " + outputPath
);
return Files.newBufferedReader(outputPath);
}
FileObject obj = processingEnv.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", annotationClassName
);
if (obj != null) {
processingEnv.getMessager().printMessage(
Kind.NOTE,
"Reading existing " + annotationClassName + " list from " + obj.toUri()
);
try {
return new BufferedReader(obj.openReader(false));
} catch (NoSuchFileException ignored) {}
}
return null;
}
private BufferedWriter createWriter() throws IOException {
if (outputPath != null) {
processingEnv.getMessager().printMessage(
Kind.NOTE, "Writing " + annotationClassName + " to " + outputPath
);
return Files.newBufferedWriter(outputPath);
}
FileObject obj = processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, "", annotationClassName
);
processingEnv.getMessager().printMessage(
Kind.NOTE, "Writing " + annotationClassName + " to " + obj.toUri()
);
return new BufferedWriter(obj.openWriter());
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.api.event;
import org.geysermc.event.bus.EventBus;
public interface FloodgateEventBus extends EventBus<Object, FloodgateSubscriber<?>> {
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.api.event;
import org.geysermc.event.subscribe.Subscriber;
public interface FloodgateSubscriber<T> extends Subscriber<T> {
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.api.event.skin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.returnsreceiver.qual.This;
import org.geysermc.event.Cancellable;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
/**
* An event that's fired when Floodgate receives a player skin. The event will be cancelled by
* default when hasSkin is true, as Floodgate by default only applies skins when the player has no
* skin applied yet.
*/
public interface SkinApplyEvent extends Cancellable {
/**
* Returns the player that will receive the skin.
*/
@NonNull FloodgatePlayer player();
/**
* Returns the skin texture currently applied to the player.
*/
@Nullable SkinData currentSkin();
/**
* Returns the skin texture to be applied to the player.
*/
@NonNull SkinData newSkin();
/**
* Sets the skin texture to be applied to the player
*
* @param skinData the skin to apply
* @return this
*/
@This SkinApplyEvent newSkin(@NonNull SkinData skinData);
interface SkinData {
/**
* Returns the value of the skin texture.
*/
@NonNull String value();
/**
* Returns the signature of the skin texture.
*/
@NonNull String signature();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,10 +34,6 @@ public enum LinkRequestResult {
* An unknown error encountered while creating / verifying the link request. * An unknown error encountered while creating / verifying the link request.
*/ */
UNKNOWN_ERROR, 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. * The specified bedrock username is already linked to a Java account.
*/ */

View File

@@ -80,18 +80,7 @@ public interface FloodgateLogger {
void trace(String message, Object... args); void trace(String message, Object... args);
/** /**
* Enables debug mode for the Floodgate logger. * Returns true if debugging is enabled
*/
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
*/ */
boolean isDebug(); boolean isDebug();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
plugins { plugins {
`java-library` `java-library`
`maven-publish`
// id("net.ltgt.errorprone") // id("net.ltgt.errorprone")
id("net.kyori.indra")
id("net.kyori.indra.git") id("net.kyori.indra.git")
} }
@@ -9,6 +9,21 @@ dependencies {
compileOnly("org.checkerframework", "checker-qual", Versions.checkerQual) 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 { tasks {
processResources { processResources {
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) { filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) {
@@ -22,14 +37,4 @@ tasks {
) )
} }
} }
compileJava {
options.encoding = Charsets.UTF_8.name()
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
} }

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ plugins {
allprojects { allprojects {
group = "org.geysermc.floodgate" 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" description = "Allows Bedrock players to join Java edition servers while keeping the server in online mode"
} }

View File

@@ -49,8 +49,7 @@ public final class BungeeInjector extends CommonPlatformInjector {
@Getter private boolean injected; @Getter private boolean injected;
@Override @Override
public boolean inject() { public void inject() {
try {
// Can everyone just switch to Velocity please :) // Can everyone just switch to Velocity please :)
Field framePrepender = ReflectionUtils.getField(PipelineUtils.class, "framePrepender"); Field framePrepender = ReflectionUtils.getField(PipelineUtils.class, "framePrepender");
@@ -65,11 +64,6 @@ public final class BungeeInjector extends CommonPlatformInjector {
BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender); BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender);
injected = true; injected = true;
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} }
@Override @Override
@@ -78,9 +72,9 @@ public final class BungeeInjector extends CommonPlatformInjector {
} }
@Override @Override
public boolean removeInjection() { public void removeInjection() {
logger.error("Floodgate cannot remove itself from Bungee without a reboot"); throw new IllegalStateException(
return false; "Floodgate cannot remove itself from Bungee without a reboot");
} }
void injectClient(Channel channel, boolean clientToProxy) { void injectClient(Channel channel, boolean clientToProxy) {

View File

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

View File

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

View File

@@ -53,21 +53,6 @@ public final class BungeePluginMessageUtils extends PluginMessageUtils implement
return; 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; UUID sourceUuid = null;
String sourceUsername = null; String sourceUsername = null;
Identity sourceIdentity = Identity.UNKNOWN; Identity sourceIdentity = Identity.UNKNOWN;
@@ -83,8 +68,9 @@ public final class BungeePluginMessageUtils extends PluginMessageUtils implement
sourceIdentity = Identity.SERVER; sourceIdentity = Identity.SERVER;
} }
Result result = channel.handleProxyCall(event.getData(), targetUuid, targetUsername, Result result = channel.handleProxyCall(
targetIdentity, sourceUuid, sourceUsername, sourceIdentity); event.getData(), sourceUuid, sourceUsername, sourceIdentity
);
event.setCancelled(!result.isAllowed()); event.setCancelled(!result.isAllowed());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.command.main;
import static org.geysermc.floodgate.util.Constants.COLOR_CHAR;
import cloud.commandframework.context.CommandContext;
import com.google.gson.JsonElement;
import com.google.inject.Inject;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.WhitelistCommand.Message;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateSubCommand;
import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpClient;
public class VersionSubcommand extends FloodgateSubCommand {
@Inject
private HttpClient httpClient;
@Inject
private FloodgateLogger logger;
@Override
public String name() {
return "version";
}
@Override
public String description() {
return "Displays version information about Floodgate";
}
@Override
public Permission permission() {
return Permission.COMMAND_MAIN_VERSION;
}
@Override
public void execute(CommandContext<UserAudience> context) {
UserAudience sender = context.getSender();
sender.sendMessage(String.format(
COLOR_CHAR + "7You're currently on " + COLOR_CHAR + "b%s" +
COLOR_CHAR + "7 (branch: " + COLOR_CHAR + "b%s" + COLOR_CHAR + "7)\n" +
COLOR_CHAR + "eFetching latest build info...",
Constants.VERSION, Constants.GIT_BRANCH
));
String baseUrl = String.format(
"https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/%s/lastSuccessfulBuild/",
Constants.GIT_BRANCH
);
httpClient.asyncGet(
baseUrl + "buildNumber",
JsonElement.class
).whenComplete((result, error) -> {
if (error != null) {
sender.sendMessage(COLOR_CHAR + "cCould not retrieve latest version info!");
error.printStackTrace();
return;
}
JsonElement response = result.getResponse();
logger.info(String.valueOf(response));
logger.info("{}", result.getHttpCode());
if (result.getHttpCode() == 404) {
sender.sendMessage(
COLOR_CHAR + "cGot a 404 (not found) while requesting the latest version." +
" Are you using a custom Floodgate version?"
);
return;
}
if (!result.isCodeOk()) {
//todo make it more generic instead of using a Whitelist command strings
logger.error(
"Got an error from requesting the latest Floodgate version: {}",
response.toString()
);
sender.sendMessage(Message.UNEXPECTED_ERROR);
return;
}
int buildNumber = response.getAsInt();
if (buildNumber > Constants.BUILD_NUMBER) {
sender.sendMessage(String.format(
COLOR_CHAR + "7There is a newer version of Floodgate available!\n" +
COLOR_CHAR + "7You are " + COLOR_CHAR + "e%s " + COLOR_CHAR + "7builds behind.\n" +
COLOR_CHAR + "7Download the latest Floodgate version here: " + COLOR_CHAR + "b%s",
buildNumber - Constants.BUILD_NUMBER, baseUrl
));
return;
}
if (buildNumber == Constants.BUILD_NUMBER) {
sender.sendMessage(COLOR_CHAR + "aYou're running the latest version of Floodgate!");
return;
}
sender.sendMessage(COLOR_CHAR + "cCannot check version for custom Floodgate versions!");
});
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event;
import com.google.inject.Singleton;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.PostOrder;
import org.geysermc.event.bus.impl.EventBusImpl;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.event.subscribe.Subscriber;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
@Singleton
@SuppressWarnings("unchecked")
public final class EventBus extends EventBusImpl<Object, FloodgateSubscriber<?>>
implements FloodgateEventBus {
@Override
protected <H, T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass,
@NonNull Subscribe subscribe,
@NonNull H listener,
@NonNull BiConsumer<H, T> handler
) {
return (B) new EventSubscriber<>(
eventClass, subscribe.postOrder(), subscribe.ignoreCancelled(), listener, handler
);
}
@Override
protected <T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass,
@NonNull Consumer<T> handler,
@NonNull PostOrder postOrder
) {
return (B) new EventSubscriber<>(eventClass, handler, postOrder);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.PostOrder;
import org.geysermc.event.subscribe.impl.SubscriberImpl;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
public final class EventSubscriber<E> extends SubscriberImpl<E> implements FloodgateSubscriber<E> {
EventSubscriber(
@NonNull Class<E> eventClass,
@NonNull Consumer<E> handler,
@NonNull PostOrder postOrder
) {
super(eventClass, handler, postOrder);
}
<H> EventSubscriber(
Class<E> eventClass,
PostOrder postOrder,
boolean ignoreCancelled,
H handlerInstance,
BiConsumer<H, E> handler
) {
super(eventClass, postOrder, ignoreCancelled, handlerInstance, handler);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event.lifecycle;
public class PostEnableEvent {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event.lifecycle;
public class ShutdownEvent {
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event.skin;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.event.util.AbstractCancellable;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public class SkinApplyEventImpl extends AbstractCancellable implements SkinApplyEvent {
private final FloodgatePlayer player;
private final SkinData currentSkin;
private SkinData newSkin;
public SkinApplyEventImpl(
@NonNull FloodgatePlayer player,
@Nullable SkinData currentSkin,
@NonNull SkinData newSkin
) {
this.player = Objects.requireNonNull(player);
this.currentSkin = currentSkin;
this.newSkin = Objects.requireNonNull(newSkin);
}
@Override
public @NonNull FloodgatePlayer player() {
return player;
}
public @Nullable SkinData currentSkin() {
return currentSkin;
}
public @NonNull SkinData newSkin() {
return newSkin;
}
public SkinApplyEventImpl newSkin(@NonNull SkinData skinData) {
this.newSkin = Objects.requireNonNull(skinData);
return this;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event.util;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.AbstractMatcher;
import org.geysermc.event.Listener;
public class ListenerAnnotationMatcher extends AbstractMatcher<TypeLiteral<?>> {
@Override
public boolean matches(TypeLiteral<?> typeLiteral) {
Class<?> rawType = typeLiteral.getRawType();
return rawType.isAnnotationPresent(Listener.class);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,17 +27,29 @@ package org.geysermc.floodgate.logger;
import static org.geysermc.floodgate.util.MessageFormatter.format; import 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.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor @Singleton
public final class JavaUtilFloodgateLogger implements FloodgateLogger { public final class JavaUtilFloodgateLogger implements FloodgateLogger {
private final Logger logger; @Inject
private final LanguageManager languageManager; @Named("logger")
private Level originLevel; 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 @Override
public void error(String message, Object... args) { public void error(String message, Object... args) {
@@ -74,19 +86,6 @@ public final class JavaUtilFloodgateLogger implements FloodgateLogger {
logger.finer(format(message, args)); 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 @Override
public boolean isDebug() { public boolean isDebug() {
return logger.getLevel() == Level.ALL; return logger.getLevel() == Level.ALL;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.platform.command;
import cloud.commandframework.context.CommandContext;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.player.UserAudience;
public abstract class FloodgateSubCommand {
public abstract String name();
public abstract String description();
public abstract Permission permission();
public abstract void execute(CommandContext<UserAudience> context);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,8 +25,16 @@
package org.geysermc.floodgate.skin; 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; import org.geysermc.floodgate.api.player.FloodgatePlayer;
public interface SkinApplier { public interface SkinApplier {
void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData); /**
* Apply a skin to a {@link FloodgatePlayer player}
*
* @param floodgatePlayer player to apply skin to
* @param skinData data for skin to apply to player
*/
void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData);
} }

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import com.google.gson.JsonObject;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
public class SkinDataImpl implements SkinData {
private final String value;
private final String signature;
public SkinDataImpl(@NonNull String value, @NonNull String signature) {
this.value = Objects.requireNonNull(value);
this.signature = Objects.requireNonNull(signature);
}
public static SkinData from(JsonObject data) {
return new SkinDataImpl(
data.get("value").getAsString(),
data.get("signature").getAsString()
);
}
@Override
public @NonNull String value() {
return value;
}
@Override
public @NonNull String signature() {
return signature;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Automatically binds an instance of itself as an eager singleton during the post-initialise stage
* of the FloodgatePlatform. Add the annotation to a class, and it should automatically create an
* instance and inject it for you.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoBind {
}

View File

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

View File

@@ -26,30 +26,27 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import java.io.InputStream; import com.google.inject.Inject;
import java.io.InputStreamReader; import com.google.inject.Singleton;
import java.io.Reader;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
/** /**
* Manages translations for strings in Floodgate * Manages translations for strings in Floodgate
*/ */
@RequiredArgsConstructor @Singleton
public final class LanguageManager { public final class LanguageManager {
private final Map<String, Properties> localeMappings = new HashMap<>(); private final Map<String, Properties> localeMappings = new HashMap<>();
private final FloodgateConfigHolder configHolder;
private final FloodgateLogger logger; @Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
/** /**
* The locale used in console and as a fallback * 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 * Tries to load the log's locale file once a string has been requested
*/ */
@Inject
private void init() { private void init() {
if (!loadLocale("en_US")) {// Fallback if (!loadLocale("en_US")) {// Fallback
logger.error("Failed to load the fallback language. This will likely cause errors!"); 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()); defaultLocale = formatLocale(config.getDefaultLocale());
if (isValidLanguage(defaultLocale)) { if (isValidLanguage(defaultLocale)) {
@@ -125,21 +113,11 @@ public final class LanguageManager {
return true; return true;
} }
InputStream localeStream = LanguageManager.class.getClassLoader().getResourceAsStream( Properties properties =
"languages/texts/" + formatLocale + ".properties"); Utils.readProperties("languages/texts/" + formatLocale + ".properties");
// load the locale if (properties != null) {
if (localeStream != null) { localeMappings.put(formatLocale, properties);
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);
return true; 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 * @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) { 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); Properties properties = localeMappings.get(locale);
String formatString = null; String formatString = null;

View File

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

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.event.lifecycle.PostEnableEvent;
@AutoBind
@Listener
public final class PostEnableMessages {
private final List<String> messages = new ArrayList<>();
@Inject private FloodgateConfig config;
@Inject private FloodgateLogger logger;
@Inject
@Named("commonScheduledPool")
private ScheduledExecutorService executorService;
public void add(String[] message, Object... args) {
StringBuilder builder = new StringBuilder();
builder.append("\n**********************************\n");
for (String part : message) {
builder.append("* ").append(part).append('\n');
}
builder.append("**********************************");
messages.add(MessageFormatter.format(builder.toString(), args));
}
@Inject
private void init() {
registerPrefixMessages();
}
private void registerPrefixMessages() {
String prefix = config.getRawUsernamePrefix();
if (prefix.isEmpty()) {
add(new String[]{
"You specified an empty prefix in your Floodgate config for Bedrock players!",
"Should a Java player join and a Bedrock player join with the same username, unwanted results and conflicts will happen!",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
});
} else if (!Utils.isUniquePrefix(prefix)) {
add(new String[]{
"The prefix you entered in your Floodgate config ({}) could lead to username conflicts!",
"Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
}, prefix, prefix, prefix, prefix);
}
if (prefix.length() >= 16) {
add(new String[]{
"The prefix you entered in your Floodgate config ({}) is longer than a Java username can be!",
"Because of this, we reset the prefix to the default Floodgate prefix (.)"
}, prefix);
} else if (prefix.length() > 2) {
// we only have to warn them if we haven't replaced the prefix
add(new String[]{
"The prefix you entered in your Floodgate config ({}) is long! ({} characters)",
"A prefix is there to prevent username conflicts. However, a long prefix makes the chance of username conflicts higher.",
"We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *"
}, prefix, prefix.length());
}
}
@Subscribe
public void onPostEnable(PostEnableEvent ignored) {
// normally proxies don't have a lot of plugins, so proxies don't need to sleep as long
executorService.schedule(
() -> messages.forEach(logger::warn),
config.isProxy() ? 2 : 5,
TimeUnit.SECONDS
);
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
public final class PrefixCheckTask {
public static void checkAndExecuteDelayed(FloodgateConfig config, FloodgateLogger logger) {
if (Utils.isUniquePrefix(config.getUsernamePrefix())) {
return;
}
new Thread(() -> {
// normally proxies don't have a lot of plugins, so proxies don't need to sleep as long
try {
Thread.sleep(config.isProxy() ? 1000 : 2000);
} catch (InterruptedException ignored) {
}
if (config.getUsernamePrefix().isEmpty()) {
logger.warn("\n" +
"**********************************\n" +
"* You specified an empty prefix in your Floodgate config for Bedrock players!\n" +
"* Should a Java player join and a Bedrock player join with the same username, unwanted results and conflicts will happen!\n" +
"* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" +
"**********************************");
return;
}
logger.warn(
"\n" +
"**********************************\n" +
"* The prefix you entered in your Floodgate config ({}) could lead to username conflicts!\n" +
"* Should a Java player join with the username {}Notch, and a Bedrock player join as Notch (who will be given the name {}Notch), unwanted results will happen!\n" +
"* We strongly recommend using . as the prefix, but other alternatives that will not conflict include: +, - and *\n" +
"**********************************",
config.getUsernamePrefix(), config.getUsernamePrefix(),
config.getUsernamePrefix(), config.getUsernamePrefix());
}).start();
}
}

View File

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

View File

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

View File

@@ -26,12 +26,12 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
public final class Constants { public final class Constants {
public static final String VERSION = "${floodgateVersion}"; public static final String VERSION = "@floodgateVersion@";
public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}"); public static final int BUILD_NUMBER = Integer.parseInt("@buildNumber@");
public static final String GIT_BRANCH = "${branch}"; public static final String GIT_BRANCH = "@branch@";
public static final int METRICS_ID = 14649; 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 DEBUG_MODE = false;
public static final boolean PRINT_ALL_PACKETS = false; public static final boolean PRINT_ALL_PACKETS = false;

View File

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

View File

@@ -1,10 +1,17 @@
val mariadbClientVersion = "2.7.4"
dependencies { dependencies {
provided(projects.core) provided(projects.core)
implementation("org.mariadb.jdbc", "mariadb-java-client" , mariadbClientVersion)
// 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" description = "The Floodgate database extension for MySQL"
relocate("org.mariadb") // relocate everything from mysql-connector-j and HikariCP
relocate("com.mysql")
relocate("com.zaxxer.hikari")
relocate("org.slf4j")

View File

@@ -25,6 +25,8 @@
package org.geysermc.floodgate.database; package org.geysermc.floodgate.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.sql.Connection; import java.sql.Connection;
@@ -43,41 +45,28 @@ import org.geysermc.floodgate.database.config.MysqlConfig;
import org.geysermc.floodgate.link.CommonPlayerLink; import org.geysermc.floodgate.link.CommonPlayerLink;
import org.geysermc.floodgate.link.LinkRequestImpl; import org.geysermc.floodgate.link.LinkRequestImpl;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.mariadb.jdbc.MariaDbPoolDataSource;
public class MysqlDatabase extends CommonPlayerLink { public class MysqlDatabase extends CommonPlayerLink {
private MariaDbPoolDataSource pool; private HikariDataSource dataSource;
@Override @Override
public void load() { public void load() {
getLogger().info("Connecting to a MySQL-like database..."); getLogger().info("Connecting to a MySQL-like database...");
try { try {
Class.forName("org.mariadb.jdbc.Driver"); MysqlConfig config = getConfig(MysqlConfig.class);
MysqlConfig databaseconfig = 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(); dataSource = new HikariDataSource(hikariConfig);
if (hostname.contains(":")) {
String[] split = hostname.split(":");
pool.setServerName(split[0]); try (Connection connection = dataSource.getConnection()) {
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()) { try (Statement statement = connection.createStatement()) {
statement.executeUpdate( statement.executeUpdate(
"CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " + "CREATE TABLE IF NOT EXISTS `LinkedPlayers` ( " +
@@ -100,8 +89,6 @@ public class MysqlDatabase extends CommonPlayerLink {
} }
} }
getLogger().info("Connected to MySQL-like database."); 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) { } catch (SQLException exception) {
getLogger().error("Error while loading database", exception); getLogger().error("Error while loading database", exception);
} }
@@ -110,14 +97,14 @@ public class MysqlDatabase extends CommonPlayerLink {
@Override @Override
public void stop() { public void stop() {
super.stop(); super.stop();
pool.close(); dataSource.close();
} }
@Override @Override
@NonNull @NonNull
public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) { public CompletableFuture<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?" "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ?"
)) { )) {
@@ -142,7 +129,7 @@ public class MysqlDatabase extends CommonPlayerLink {
@NonNull @NonNull
public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) { public CompletableFuture<Boolean> isLinkedPlayer(@NonNull UUID playerId) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?" "SELECT * FROM `LinkedPlayers` WHERE `bedrockId` = ? OR `javaUniqueId` = ?"
)) { )) {
@@ -174,7 +161,7 @@ public class MysqlDatabase extends CommonPlayerLink {
} }
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) { private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " + "INSERT INTO `LinkedPlayers` VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE " +
"`javaUniqueId`=VALUES(`javaUniqueId`), " + "`javaUniqueId`=VALUES(`javaUniqueId`), " +
@@ -195,7 +182,7 @@ public class MysqlDatabase extends CommonPlayerLink {
@NonNull @NonNull
public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) { public CompletableFuture<Void> unlinkPlayer(@NonNull UUID javaId) {
return CompletableFuture.runAsync(() -> { return CompletableFuture.runAsync(() -> {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?" "DELETE FROM `LinkedPlayers` WHERE `javaUniqueId` = ? OR `bedrockId` = ?"
)) { )) {
@@ -216,7 +203,8 @@ public class MysqlDatabase extends CommonPlayerLink {
public CompletableFuture<String> createLinkRequest( public CompletableFuture<String> createLinkRequest(
@NonNull UUID javaId, @NonNull UUID javaId,
@NonNull String javaUsername, @NonNull String javaUsername,
@NonNull String bedrockUsername) { @NonNull String bedrockUsername
) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode(); String linkCode = createCode();
@@ -230,8 +218,9 @@ public class MysqlDatabase extends CommonPlayerLink {
String javaUsername, String javaUsername,
UUID javaId, UUID javaId,
String linkCode, String linkCode,
String bedrockUsername) { String bedrockUsername
try (Connection connection = pool.getConnection()) { ) {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " + "INSERT INTO `LinkedPlayersRequest` VALUES (?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " + "ON DUPLICATE KEY UPDATE " +
@@ -254,7 +243,7 @@ public class MysqlDatabase extends CommonPlayerLink {
} }
private void removeLinkRequest(String javaUsername) { private void removeLinkRequest(String javaUsername) {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" "DELETE FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) { )) {
@@ -272,7 +261,8 @@ public class MysqlDatabase extends CommonPlayerLink {
@NonNull UUID bedrockId, @NonNull UUID bedrockId,
@NonNull String javaUsername, @NonNull String javaUsername,
@NonNull String bedrockUsername, @NonNull String bedrockUsername,
@NonNull String code) { @NonNull String code
) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername); LinkRequest request = getLinkRequest0(javaUsername);
@@ -297,7 +287,7 @@ public class MysqlDatabase extends CommonPlayerLink {
} }
private LinkRequest getLinkRequest0(String javaUsername) { private LinkRequest getLinkRequest0(String javaUsername) {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?" "SELECT * FROM `LinkedPlayersRequest` WHERE `javaUsername` = ?"
)) { )) {
@@ -322,7 +312,7 @@ public class MysqlDatabase extends CommonPlayerLink {
} }
public void cleanLinkRequests() { public void cleanLinkRequests() {
try (Connection connection = pool.getConnection()) { try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement query = connection.prepareStatement( try (PreparedStatement query = connection.prepareStatement(
"DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?" "DELETE FROM `LinkedPlayersRequest` WHERE `requestTime` < ?"
)) { )) {
@@ -347,5 +337,4 @@ public class MysqlDatabase extends CommonPlayerLink {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes); ByteBuffer buf = ByteBuffer.wrap(uuidBytes);
return new UUID(buf.getLong(), buf.getLong()); return new UUID(buf.getLong(), buf.getLong());
} }
} }

View File

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

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

10
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright <EFBFBD> 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
# * functions; # * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»; # <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
# * compound commands having a testable exit status, especially «case»; # * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
# * various built-in commands including «command», «set», and «ulimit». # * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
# #
# Important for patching: # Important for patching:
# #

View File

@@ -1,3 +1,4 @@
@file:Suppress("UnstableApiUsage")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement { dependencyResolutionManagement {
@@ -14,7 +15,19 @@ dependencyResolutionManagement {
} }
// Paper, Velocity // 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 // Spigot
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") {
mavenContent { snapshotsOnly() } mavenContent { snapshotsOnly() }
@@ -43,7 +56,8 @@ pluginManagement {
gradlePluginPortal() gradlePluginPortal()
} }
plugins { plugins {
id("net.kyori.blossom") version "1.2.0" id("net.kyori.indra")
id("net.kyori.indra.git")
} }
includeBuild("build-logic") includeBuild("build-logic")
} }
@@ -51,6 +65,7 @@ pluginManagement {
rootProject.name = "floodgate-parent" rootProject.name = "floodgate-parent"
include(":api") include(":api")
include(":ap")
include(":core") include(":core")
include(":bungee") include(":bungee")
include(":spigot") include(":spigot")

View File

@@ -27,6 +27,7 @@ package org.geysermc.floodgate;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
@@ -43,7 +44,7 @@ import org.geysermc.floodgate.util.SpigotProtocolSupportHandler;
import org.geysermc.floodgate.util.SpigotProtocolSupportListener; import org.geysermc.floodgate.util.SpigotProtocolSupportListener;
public final class SpigotPlugin extends JavaPlugin { public final class SpigotPlugin extends JavaPlugin {
private SpigotPlatform platform; private FloodgatePlatform platform;
private Injector injector; private Injector injector;
@Override @Override
@@ -54,7 +55,7 @@ public final class SpigotPlugin extends JavaPlugin {
new SpigotPlatformModule(this) new SpigotPlatformModule(this)
); );
platform = injector.getInstance(SpigotPlatform.class); platform = injector.getInstance(FloodgatePlatform.class);
long endCtm = System.currentTimeMillis(); long endCtm = System.currentTimeMillis();
injector.getInstance(FloodgateLogger.class) injector.getInstance(FloodgateLogger.class)
@@ -66,14 +67,18 @@ public final class SpigotPlugin extends JavaPlugin {
boolean usePaperListener = ReflectionUtils.getClassSilently( boolean usePaperListener = ReflectionUtils.getClassSilently(
"com.destroystokyo.paper.event.profile.PreFillProfileEvent") != null; "com.destroystokyo.paper.event.profile.PreFillProfileEvent") != null;
try {
platform.enable( platform.enable(
new SpigotCommandModule(this), new SpigotCommandModule(this),
new SpigotAddonModule(), new SpigotAddonModule(),
new PluginMessageModule(), new PluginMessageModule(),
(usePaperListener ? new PaperListenerModule() : new SpigotListenerModule()) (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) injector.getInstance(HandshakeHandlers.class)
.addHandshakeHandler(injector.getInstance(SpigotHandshakeHandler.class)); .addHandshakeHandler(injector.getInstance(SpigotHandshakeHandler.class));

View File

@@ -25,25 +25,28 @@
package org.geysermc.floodgate.inject.spigot; 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.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.util.ClassNames; import org.geysermc.floodgate.util.ClassNames;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@RequiredArgsConstructor @Singleton
public final class SpigotInjector extends CommonPlatformInjector { public final class SpigotInjector extends CommonPlatformInjector {
@Inject private FloodgateLogger logger;
private Object serverConnection; private Object serverConnection;
private String injectedFieldName; private String injectedFieldName;
@@ -51,12 +54,16 @@ public final class SpigotInjector extends CommonPlatformInjector {
@Override @Override
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public boolean inject() throws Exception { public void inject() throws Exception {
if (isInjected()) { if (isInjected()) {
return true; return;
}
Object serverConnection = getServerConnection();
if (serverConnection == null) {
throw new RuntimeException("Unable to find server connection");
} }
if (getServerConnection() != null) {
for (Field field : serverConnection.getClass().getDeclaredFields()) { for (Field field : serverConnection.getClass().getDeclaredFields()) {
if (field.getType() == List.class) { if (field.getType() == List.class) {
field.setAccessible(true); field.setAccessible(true);
@@ -94,12 +101,10 @@ public final class SpigotInjector extends CommonPlatformInjector {
field.set(serverConnection, newList); field.set(serverConnection, newList);
injected = true; injected = true;
return true; return;
} }
} }
} }
return false;
}
public void injectClient(ChannelFuture future) { public void injectClient(ChannelFuture future) {
future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() {
@@ -120,36 +125,48 @@ public final class SpigotInjector extends CommonPlatformInjector {
} }
@Override @Override
public boolean removeInjection() throws Exception { public void removeInjection() {
if (!isInjected()) { if (!isInjected()) {
return true; return;
} }
// remove injection from clients // let's change the list back to the original first
for (Channel channel : getInjectedClients()) { // so that new connections are not handled through our custom list
removeAddonsCall(channel);
}
getInjectedClients().clear();
// and change the list back to the original
Object serverConnection = getServerConnection(); Object serverConnection = getServerConnection();
if (serverConnection != null) { if (serverConnection != null) {
Field field = ReflectionUtils.getField(serverConnection.getClass(), injectedFieldName); Field field = ReflectionUtils.getField(serverConnection.getClass(), injectedFieldName);
List<?> list = (List<?>) ReflectionUtils.getValue(serverConnection, field); Object value = ReflectionUtils.getValue(serverConnection, field);
if (list instanceof CustomList) { if (value instanceof CustomList) {
CustomList customList = (CustomList) list; // 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()); ReflectionUtils.setValue(serverConnection, field, customList.getOriginalList());
customList.clear(); return;
customList.addAll(list);
} }
// 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; injected = false;
return true;
} }
public Object getServerConnection() throws IllegalAccessException, InvocationTargetException { private Object getServerConnection() {
if (serverConnection != null) { if (serverConnection != null) {
return serverConnection; return serverConnection;
} }
@@ -158,14 +175,11 @@ public final class SpigotInjector extends CommonPlatformInjector {
// method by CraftBukkit to get the instance of the MinecraftServer // method by CraftBukkit to get the instance of the MinecraftServer
Object minecraftServerInstance = ReflectionUtils.invokeStatic(minecraftServer, "getServer"); Object minecraftServerInstance = ReflectionUtils.invokeStatic(minecraftServer, "getServer");
for (Method method : minecraftServer.getDeclaredMethods()) { Method method = ReflectionUtils.getMethodThatReturns(
if (ClassNames.SERVER_CONNECTION.equals(method.getReturnType())) { minecraftServer, ClassNames.SERVER_CONNECTION, true
// making sure that it's a getter );
if (method.getParameterTypes().length == 0) {
serverConnection = method.invoke(minecraftServerInstance); serverConnection = ReflectionUtils.invoke(minecraftServerInstance, method);
}
}
}
return serverConnection; return serverConnection;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,17 +27,27 @@ package org.geysermc.floodgate.logger;
import static org.geysermc.floodgate.util.MessageFormatter.format; 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.Level;
import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.slf4j.Logger; import org.slf4j.Logger;
@RequiredArgsConstructor @Singleton
public final class Slf4jFloodgateLogger implements FloodgateLogger { public final class Slf4jFloodgateLogger implements FloodgateLogger {
private final Logger logger; @Inject private Logger logger;
private final LanguageManager languageManager; 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 @Override
public void error(String message, Object... args) { public void error(String message, Object... args) {
@@ -74,20 +84,6 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger {
logger.trace(message, args); 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 @Override
public boolean isDebug() { public boolean isDebug() {
return logger.isDebugEnabled(); return logger.isDebugEnabled();

View File

@@ -56,11 +56,9 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration;
import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageRegistration;
import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageUtils; import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageUtils;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.VelocityCommandUtil; import org.geysermc.floodgate.util.VelocityCommandUtil;
import org.geysermc.floodgate.util.VelocityPlatformUtils; import org.geysermc.floodgate.util.VelocityPlatformUtils;
import org.geysermc.floodgate.util.VelocitySkinApplier; import org.geysermc.floodgate.util.VelocitySkinApplier;
import org.slf4j.Logger;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class VelocityPlatformModule extends AbstractModule { public final class VelocityPlatformModule extends AbstractModule {
@@ -70,6 +68,8 @@ public final class VelocityPlatformModule extends AbstractModule {
protected void configure() { protected void configure() {
bind(CommandUtil.class).to(VelocityCommandUtil.class); bind(CommandUtil.class).to(VelocityCommandUtil.class);
bind(PlatformUtils.class).to(VelocityPlatformUtils.class); bind(PlatformUtils.class).to(VelocityPlatformUtils.class);
bind(FloodgateLogger.class).to(Slf4jFloodgateLogger.class);
bind(SkinApplier.class).to(VelocitySkinApplier.class);
} }
@Provides @Provides
@@ -89,12 +89,6 @@ public final class VelocityPlatformModule extends AbstractModule {
return commandManager; return commandManager;
} }
@Provides
@Singleton
public FloodgateLogger floodgateLogger(Logger logger, LanguageManager languageManager) {
return new Slf4jFloodgateLogger(logger, languageManager);
}
/* /*
Commands / Listeners Commands / Listeners
*/ */
@@ -119,20 +113,14 @@ public final class VelocityPlatformModule extends AbstractModule {
return new VelocityPluginMessageRegistration(proxy); return new VelocityPluginMessageRegistration(proxy);
} }
@Provides
@Singleton
public SkinApplier skinApplier(ProxyServer server) {
return new VelocitySkinApplier(server);
}
/* /*
DebugAddon / PlatformInjector DebugAddon / PlatformInjector
*/ */
@Provides @Provides
@Singleton @Singleton
public CommonPlatformInjector platformInjector(ProxyServer server, FloodgateLogger logger) { public CommonPlatformInjector platformInjector(ProxyServer server) {
return new VelocityInjector(server, logger); return new VelocityInjector(server);
} }
@Provides @Provides

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