diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 77109099..4edc17e3 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,6 +1,6 @@ name: Build Pull Request -on: [pull_request] +on: [ pull_request ] jobs: build: @@ -8,34 +8,35 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'temurin' + java-version: '8' + cache: 'gradle' + - name: Build with Maven - run: mvn -B package + run: ./gradlew build + - name: Archive artifacts (Floodgate Bungee) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Floodgate Bungee - path: bungee/target/floodgate-bungee.jar + path: bungee/build/libs/floodgate-bungee.jar + - name: Archive artifacts (Floodgate Spigot) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Floodgate Spigot - path: spigot/target/floodgate-spigot.jar + path: spigot/build/libs/floodgate-spigot.jar + - name: Archive artifacts (Floodgate Velocity) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: success() with: name: Floodgate Velocity - path: velocity/target/floodgate-velocity.jar + path: velocity/build/libs/floodgate-velocity.jar \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 46351173..80976be7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,11 +11,18 @@ pipeline { stage ('Build') { steps { sh 'git submodule update --init --recursive' - sh './gradlew clean build' + rtGradleRun( + usesPlugin: true, + tool: 'Gradle 7', + buildFile: 'build.gradle.kts', + tasks: 'clean build', + ) } post { success { - archiveArtifacts artifacts: '**/build/libs/floodgate-*.jar', excludes: "**/build/libs/floodgate-api.jar,**/build/libs/floodgate-core.jar", fingerprint: true + archiveArtifacts artifacts: '**/build/libs/floodgate-*.jar', + excludes: '**/floodgate-parent-*.jar,**/floodgate-api.jar,**/floodgate-core.jar', + fingerprint: true } } } @@ -45,7 +52,7 @@ pipeline { rootDir: "", useWrapper: true, buildFile: 'build.gradle.kts', - tasks: 'build artifactoryPublish', + tasks: 'artifactoryPublish', deployerId: "GRADLE_DEPLOYER", resolverId: "GRADLE_RESOLVER" ) diff --git a/README.md b/README.md index 5a9d098e..652325ce 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) [![HitCount](https://hits.dwyl.com/GeyserMC/Floodgate.svg)](http://hits.dwyl.com/GeyserMC/Floodgate) +[Download](https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/) + Hybrid mode plugin to allow for connections from [Geyser](https://github.com/GeyserMC/Geyser) to join online mode servers. Geyser is an open collaboration project by [CubeCraft Games](https://cubecraft.net). -See the [Floodgate](https://github.com/GeyserMC/Geyser/wiki/Floodgate) page in the Geyser Wiki for more info about the what Floodgate is, how you setup Floodgate and known issues/caveats. - -See the [Floodgate wiki](https://github.com/GeyserMC/Floodgate/wiki) (currently work in progress) for a more in-depth look into Floodgate, how it works and the Floodgate API. +See the [Floodgate](https://wiki.geysermc.org/floodgate/) section in the GeyserMC Wiki for more info about what Floodgate is, how you setup Floodgate and known issues/caveats. Additionally, it includes a more in-depth look into how Floodgate works and the Floodgate API. diff --git a/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java b/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java index d550cbcb..ee991b78 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java +++ b/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java @@ -34,7 +34,7 @@ public interface InjectorAddon { * used for third party things. * * @param channel the channel that the injector is injecting - * @param toServer if the the connection is between a proxy and a server + * @param toServer if the connection is between a proxy and a server */ void onInject(Channel channel, boolean toServer); @@ -43,7 +43,7 @@ public interface InjectorAddon { * closed connection (if it is injected), so it'll also run this method for closed connections * between a server and the proxy (when Floodgate is running on a proxy). * - * @param channel the channel that the injecor injected + * @param channel the channel that the injector injected */ default void onChannelClosed(Channel channel) { } diff --git a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java index 25bedbe9..04e1d4d0 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java +++ b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java @@ -51,7 +51,7 @@ public class PropertyKey { this.removable = removable; } - public Result isAddAllowed(Object obj) { //todo use for add and remove + public Result isAddAllowed(Object obj) { if (obj instanceof PropertyKey) { PropertyKey propertyKey = (PropertyKey) obj; diff --git a/build-logic/src/main/kotlin/Versions.kt b/build-logic/src/main/kotlin/Versions.kt index 74b115c6..34392cfa 100644 --- a/build-logic/src/main/kotlin/Versions.kt +++ b/build-logic/src/main/kotlin/Versions.kt @@ -26,15 +26,15 @@ object Versions { const val geyserVersion = "2.0.1-cumulus-SNAPSHOT" const val cumulusVersion = "1.1-SNAPSHOT" - const val spigotVersion = "1.13-R0.1-SNAPSHOT" + const val configUtilsVersion = "1.0-SNAPSHOT" + const valspigotVersion = "1.13-R0.1-SNAPSHOT" const val fastutilVersion = "8.5.3" const val lombokVersion = "1.18.20" const val guiceVersion = "5.0.1" const val nettyVersion = "4.1.49.Final" const val snakeyamlVersion = "1.28" const val cloudVersion = "1.5.0" - const val adventureApiVersion = "4.9.1" - const val adventurePlatformVersion = "4.0.0" + const val bstatsVersion = "3.0.0" const val javaWebsocketVersion = "1.5.2" diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt index 2cbbacc8..69368784 100644 --- a/build-logic/src/main/kotlin/extensions.kt +++ b/build-logic/src/main/kotlin/extensions.kt @@ -23,30 +23,37 @@ * @link https://github.com/GeyserMC/Floodgate */ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import net.kyori.indra.git.IndraGitExtension import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency -import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.the +fun Project.isSnapshot(): Boolean = + version.toString().endsWith("-SNAPSHOT") + +fun Project.fullVersion(): String { + var version = version.toString() + if (version.endsWith("-SNAPSHOT")) { + version += " (b${buildNumberAsString()}-${lastCommitHash()})" + } + return version +} + fun Project.lastCommitHash(): String? = the().commit()?.name?.substring(0, 7) // retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project // some properties might be specific to Jenkins fun Project.branchName(): String = - System.getProperty("GIT_BRANCH", "local/dev") + System.getenv("GIT_BRANCH") ?: "local/dev" fun Project.buildNumber(): Int = - Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")) + Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1") -fun Project.relocate(pattern: String) { - tasks.named("shadowJar") { - relocate(pattern, "org.geysermc.floodgate.shaded.$pattern") - } -} +fun Project.buildNumberAsString(): String = + buildNumber().takeIf { it != -1 }?.toString() ?: "??" val providedDependencies = mutableMapOf>() +val relocatedPackages = mutableMapOf>() fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) { providedDependencies.getOrPut(project.name) { mutableSetOf() } @@ -59,5 +66,10 @@ fun Project.provided(pattern: String, name: String, version: String, excludedOn: fun Project.provided(dependency: ProjectDependency) = provided(dependency.group!!, dependency.name, dependency.version!!) + +fun Project.relocate(pattern: String) = + relocatedPackages.getOrPut(project.name) { mutableSetOf() } + .add(pattern) + private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String = if (excludedOn and bit > 0) section else "" \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts index 168456d8..0dba1832 100644 --- a/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.base-conventions.gradle.kts @@ -2,6 +2,7 @@ plugins { `java-library` `maven-publish` // id("net.ltgt.errorprone") + id("net.kyori.indra.git") } dependencies { @@ -14,7 +15,7 @@ tasks { expand( "id" to "floodgate", "name" to "floodgate", - "version" to project.version, + "version" to fullVersion(), "description" to project.description, "url" to "https://geysermc.org", "author" to "GeyserMC" @@ -31,12 +32,4 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() -} - -publishing { - publications.create("mavenJava") { - groupId = project.group as String - artifactId = "floodgate-" + project.name - version = project.version as String - } } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts new file mode 100644 index 00000000..60c3e272 --- /dev/null +++ b/build-logic/src/main/kotlin/floodgate.publish-conventions.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id("floodgate.shadow-conventions") + id("com.jfrog.artifactory") + id("maven-publish") +} + +publishing { + publications { + create("mavenJava") { + groupId = project.group as String + artifactId = project.name + version = project.version as String + + artifact(tasks["shadowJar"]) + artifact(tasks["sourcesJar"]) + } + } +} + +artifactory { + setContextUrl("https://repo.opencollab.dev/artifactory") + publish { + repository { + setRepoKey(if (isSnapshot()) "maven-snapshots" else "maven-releases") + setMavenCompatible(true) + } + defaults { + publications("mavenJava") + setPublishArtifacts(true) + setPublishPom(true) + setPublishIvy(false) + } + } +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts index 5c7d4a1f..a0bd500b 100644 --- a/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/floodgate.shadow-conventions.gradle.kts @@ -3,7 +3,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { id("floodgate.base-conventions") id("com.github.johnrengelman.shadow") - id("com.jfrog.artifactory") } tasks { @@ -25,6 +24,12 @@ tasks { exclude(dependency(string)) } } + + // relocations made in included project dependencies are for whatever reason not + // forwarded to the project implementing the dependency. + // (e.g. a relocation in `core` will relocate for core. But when you include `core` in + // for example Velocity, the relocation will be gone for Velocity) + addRelocations(project, sJar) } } named("build") { @@ -32,24 +37,18 @@ tasks { } } -publishing { - publications.named("mavenJava") { - artifact(tasks["shadowJar"]) - artifact(tasks["sourcesJar"]) +fun addRelocations(project: Project, shadowJar: ShadowJar) { + callAddRelocations(project.configurations.api.get(), shadowJar) + callAddRelocations(project.configurations.implementation.get(), shadowJar) + + relocatedPackages[project.name]?.forEach { pattern -> + println("Relocating $pattern for ${shadowJar.project.name}") + shadowJar.relocate(pattern, "org.geysermc.floodgate.shadow.$pattern") } } -artifactory { - publish { - repository { - setRepoKey("maven-snapshots") - setMavenCompatible(true) - } - defaults { - publishConfigs("archives") - setPublishArtifacts(true) - setPublishPom(true) - setPublishIvy(false) - } - } -} \ No newline at end of file +fun callAddRelocations(configuration: Configuration, shadowJar: ShadowJar) = + configuration.dependencies.forEach { + if (it is ProjectDependency) + addRelocations(it.dependencyProject, shadowJar) + } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 8b6c3715..c0652297 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("floodgate.build-logic") // id("com.github.spotbugs") version "4.8.0" apply false id("io.freefair.lombok") version "6.3.0" apply false +// checkstyle } allprojects { @@ -11,7 +12,10 @@ allprojects { description = "Allows Bedrock players to join Java edition servers while keeping the server in online mode" } -val platforms = setOf( +val deployProjects = setOf( + projects.api, + // for future Floodgate integration + Fabric + projects.core, projects.bungee, projects.spigot, projects.velocity @@ -19,28 +23,32 @@ val platforms = setOf( //todo re-add pmd and organisation/license/sdcm/issuemanagement stuff -val api: Project = projects.api.dependencyProject - subprojects { -// apply(plugin = "pmd") // apply(plugin = "com.github.spotbugs") apply { plugin("java-library") +// plugin("checkstyle") plugin("io.freefair.lombok") plugin("floodgate.build-logic") } +// checkstyle { +// toolVersion = "9.3" +// configFile = rootProject.projectDir.resolve("checkstyle.xml") +// maxErrors = 0 +// maxWarnings = 0 +// } + val relativePath = projectDir.relativeTo(rootProject.projectDir).path if (relativePath.startsWith("database" + File.separator)) { group = rootProject.group as String + ".database" plugins.apply("floodgate.database-conventions") - } else { - when (this) { - in platforms -> plugins.apply("floodgate.shadow-conventions") - api -> plugins.apply("floodgate.shadow-conventions") - else -> plugins.apply("floodgate.base-conventions") - } + } + + when (this) { + in deployProjects -> plugins.apply("floodgate.publish-conventions") + else -> plugins.apply("floodgate.base-conventions") } } \ No newline at end of file diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index d98b65e1..b362a0ac 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -4,10 +4,7 @@ var guavaVersion = "21.0" dependencies { api(projects.core) - implementation("cloud.commandframework", "cloud-bungee", Versions.cloudVersion) - implementation("net.kyori", "adventure-text-serializer-gson", Versions.adventureApiVersion) - implementation("net.kyori", "adventure-text-serializer-bungeecord", Versions.adventurePlatformVersion) } relocate("com.google.inject") diff --git a/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java index 4178490b..7181edd2 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java +++ b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java @@ -59,7 +59,7 @@ public final class BungeeInjector extends CommonPlatformInjector { // (Instead of just replacing the ChannelInitializer which is only called for // player <-> proxy) BungeeCustomPrepender customPrepender = new BungeeCustomPrepender( - this, ReflectionUtils.getCastedValue(null, framePrepender) + this, ReflectionUtils.castedStaticValue(framePrepender) ); BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender); diff --git a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java index 9dff252b..e62a1776 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java @@ -36,6 +36,7 @@ import java.util.UUID; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.connection.InitialHandler; @@ -45,8 +46,9 @@ import net.md_5.bungee.netty.ChannelWrapper; import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.player.FloodgatePlayerImpl; -import org.geysermc.floodgate.util.BungeeCommandUtil; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.skin.SkinData; import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.ReflectionUtils; @@ -64,9 +66,11 @@ public final class BungeeListener implements Listener { checkNotNull(PLAYER_NAME, "Initial name field cannot be null"); } + @Inject private ProxyFloodgateConfig config; @Inject private ProxyFloodgateApi api; @Inject private LanguageManager languageManager; @Inject private FloodgateLogger logger; + @Inject private SkinApplier skinApplier; @Inject @Named("playerAttribute") @@ -121,10 +125,19 @@ public final class BungeeListener implements Listener { } } + @EventHandler(priority = EventPriority.LOWEST) + public void onPostLogin(PostLoginEvent event) { + // To fix the February 2 2022 Mojang authentication changes + if (!config.isSendFloodgateData()) { + FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId()); + if (player != null && !player.isLinked()) { + skinApplier.applySkin(player, new SkinData("", "")); + } + } + } + @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerDisconnect(PlayerDisconnectEvent event) { api.playerRemoved(event.getPlayer().getUniqueId()); - - BungeeCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java index dccda6b2..cbe9b5e5 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java @@ -46,6 +46,7 @@ import org.geysermc.floodgate.logger.JavaUtilFloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.platform.util.PlatformUtils; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.pluginmessage.BungeePluginMessageRegistration; @@ -55,12 +56,18 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.util.BungeeCommandUtil; +import org.geysermc.floodgate.util.BungeePlatformUtils; import org.geysermc.floodgate.util.LanguageManager; @RequiredArgsConstructor public final class BungeePlatformModule extends AbstractModule { private final BungeePlugin plugin; + @Override + protected void configure() { + bind(PlatformUtils.class).to(BungeePlatformUtils.class); + } + @Provides @Singleton public Plugin bungeePlugin() { @@ -83,7 +90,7 @@ public final class BungeePlatformModule extends AbstractModule { CommandManager commandManager = new BungeeCommandManager<>( plugin, CommandExecutionCoordinator.simpleCoordinator(), - commandUtil::getAudience, + commandUtil::getUserAudience, audience -> (CommandSender) audience.source() ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); @@ -92,11 +99,8 @@ public final class BungeePlatformModule extends AbstractModule { @Provides @Singleton - public CommandUtil commandUtil( - FloodgateApi api, - FloodgateLogger logger, - LanguageManager languageManager) { - return new BungeeCommandUtil(plugin.getProxy(), api, logger, languageManager); + public CommandUtil commandUtil(FloodgateApi api, LanguageManager languageManager) { + return new BungeeCommandUtil(languageManager, plugin.getProxy(), api); } @Provides diff --git a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java index e0b67f03..3a10b764 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java +++ b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java @@ -25,6 +25,10 @@ package org.geysermc.floodgate.pluginmessage; +import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType; +import static org.geysermc.floodgate.util.ReflectionUtils.setValue; + +import java.lang.reflect.Field; import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -38,6 +42,12 @@ import org.geysermc.floodgate.skin.SkinData; @RequiredArgsConstructor public final class BungeeSkinApplier implements SkinApplier { + private static final Field LOGIN_RESULT; + + static { + LOGIN_RESULT = getFieldOfType(InitialHandler.class, LoginResult.class); + } + private final FloodgateLogger logger; @Override @@ -61,6 +71,7 @@ public final class BungeeSkinApplier implements SkinApplier { if (loginResult == null) { // id and name are unused and properties will be overridden loginResult = new LoginResult(null, null, null); + setValue(handler, LOGIN_RESULT, loginResult); } Property property = new Property("textures", skinData.getValue(), skinData.getSignature()); diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java index e614c351..8d1e7c1c 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java +++ b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java @@ -25,42 +25,29 @@ package org.geysermc.floodgate.util; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; -import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; -import org.geysermc.floodgate.util.BungeeUserAudience.BungeeConsoleAudience; -import org.geysermc.floodgate.util.BungeeUserAudience.BungeePlayerAudience; - -@RequiredArgsConstructor -public final class BungeeCommandUtil implements CommandUtil { - public static final @NonNull Map AUDIENCE_CACHE = new HashMap<>(); - private static UserAudience console; +import org.geysermc.floodgate.player.UserAudience.ConsoleAudience; +import org.geysermc.floodgate.player.UserAudience.PlayerAudience; +public final class BungeeCommandUtil extends CommandUtil { private final ProxyServer server; - private final FloodgateApi api; + private UserAudience console; - private final FloodgateLogger logger; - private final LanguageManager manager; + public BungeeCommandUtil(LanguageManager manager, ProxyServer server, FloodgateApi api) { + super(manager, api); + this.server = server; + } @Override - public @NonNull UserAudience getAudience(@NonNull Object sourceObj) { + public @NonNull UserAudience getUserAudience(@NonNull Object sourceObj) { if (!(sourceObj instanceof CommandSender)) { throw new IllegalArgumentException("Can only work with CommandSource!"); } @@ -70,7 +57,7 @@ public final class BungeeCommandUtil implements CommandUtil { if (console != null) { return console; } - return console = new BungeeConsoleAudience(source, this); + return console = new ConsoleAudience(source, this); } ProxiedPlayer player = (ProxiedPlayer) source; @@ -78,82 +65,39 @@ public final class BungeeCommandUtil implements CommandUtil { String username = player.getName(); String locale = Utils.getLocale(player.getLocale()); - return AUDIENCE_CACHE.computeIfAbsent(uuid, - $ -> new BungeePlayerAudience(uuid, username, locale, source, true, this)); + return new PlayerAudience(uuid, username, locale, source, this, true); } @Override - public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { - ProxiedPlayer player = server.getPlayer(username); - return player != null ? getAudience(player) : null; + protected String getUsernameFromSource(@NonNull Object source) { + return ((ProxiedPlayer) source).getName(); } @Override - public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { - return new BungeePlayerAudience(null, username, null, null, false, this); + protected UUID getUuidFromSource(@NonNull Object source) { + return ((ProxiedPlayer) source).getUniqueId(); } @Override - public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { + protected Collection getOnlinePlayers() { + return server.getPlayers(); + } + + @Override + public Object getPlayerByUuid(@NonNull UUID uuid) { ProxiedPlayer player = server.getPlayer(uuid); - return player != null ? getAudience(player) : null; + return player != null ? player : uuid; } @Override - public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { - return new BungeePlayerAudience(uuid, null, null, null, false, this); - } - - @Override - public @NonNull Collection getOnlineUsernames(@NonNull PlayerType limitTo) { - Collection players = server.getPlayers(); - - Collection usernames = new ArrayList<>(); - switch (limitTo) { - case ALL_PLAYERS: - for (ProxiedPlayer player : players) { - usernames.add(player.getName()); - } - break; - case ONLY_JAVA: - for (ProxiedPlayer player : players) { - if (!api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getName()); - } - } - break; - case ONLY_BEDROCK: - for (ProxiedPlayer player : players) { - if (api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getName()); - } - } - break; - default: - throw new IllegalStateException("Unknown PlayerType"); - } - return usernames; + public Object getPlayerByUsername(@NonNull String username) { + ProxiedPlayer player = server.getPlayer(username); + return player != null ? player : username; } @Override public boolean hasPermission(Object player, String permission) { - return cast(player).hasPermission(permission); - } - - @Override - public Collection getOnlinePlayersWithPermission(String permission) { - List players = new ArrayList<>(); - for (ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) { - if (hasPermission(player, permission)) { - players.add(player); - } - } - return players; - } - - @Override - public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { - ((CommandSender) target).sendMessage(translateAndTransform(locale, message, args)); + return ((CommandSender) player).hasPermission(permission); } @Override @@ -162,23 +106,10 @@ public final class BungeeCommandUtil implements CommandUtil { } @Override - public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { - cast(player).disconnect(translateAndTransform(locale, message, args)); - } - - public BaseComponent[] translateAndTransform( - String locale, - TranslatableMessage message, - Object... args) { - return TextComponent.fromLegacyText(message.translateMessage(manager, locale, args)); - } - - protected ProxiedPlayer cast(Object player) { - try { - return (ProxiedPlayer) player; - } catch (ClassCastException exception) { - logger.error("Failed to cast {} to ProxiedPlayer", player.getClass().getName()); - throw exception; + public void kickPlayer(Object player, String message) { + // can also be a console + if (player instanceof ProxiedPlayer) { + ((ProxiedPlayer) player).disconnect(message); } } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeePlatformUtils.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeePlatformUtils.java new file mode 100644 index 00000000..fae9a83c --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/util/BungeePlatformUtils.java @@ -0,0 +1,76 @@ +/* + * 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.reflect.Field; +import java.util.List; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.protocol.ProtocolConstants; +import org.geysermc.floodgate.platform.util.PlatformUtils; + +@SuppressWarnings("ConstantConditions") +public final class BungeePlatformUtils extends PlatformUtils { + private static final String LATEST_SUPPORTED_VERSION; + private final ProxyServer proxyServer = ProxyServer.getInstance(); + + static { + int protocolNumber = -1; + String versionName = ""; + + for (Field field : ProtocolConstants.class.getFields()) { + if (!field.getName().startsWith("MINECRAFT_")) { + continue; + } + + int fieldValue = ReflectionUtils.castedStaticValue(field); + if (fieldValue > protocolNumber) { + protocolNumber = fieldValue; + versionName = field.getName().substring(10).replace('_', '.'); + } + } + + if (protocolNumber == -1) { + List versions = ProtocolConstants.SUPPORTED_VERSIONS; + versionName = versions.get(versions.size() - 1); + } + LATEST_SUPPORTED_VERSION = versionName; + } + + @Override + public AuthType authType() { + return proxyServer.getConfig().isOnlineMode() ? AuthType.ONLINE : AuthType.OFFLINE; + } + + @Override + public String minecraftVersion() { + return LATEST_SUPPORTED_VERSION; + } + + @Override + public String serverImplementationName() { + return proxyServer.getName(); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java deleted file mode 100644 index 3554549d..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeUserAudience.java +++ /dev/null @@ -1,159 +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 java.util.UUID; -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.audience.ForwardingAudience; -import net.kyori.adventure.audience.MessageType; -import net.kyori.adventure.identity.Identity; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; -import org.geysermc.floodgate.player.UserAudience; - -@RequiredArgsConstructor -public class BungeeUserAudience implements UserAudience, ForwardingAudience.Single { - private final UUID uuid; - private final String locale; - private final CommandSender source; - private final CommandUtil commandUtil; - - @Override - public @NonNull UUID uuid() { - return uuid; - } - - @Override - public @NonNull String username() { - return source.getName(); - } - - @Override - public @NonNull String locale() { - return locale; - } - - @Override - public @NonNull CommandSender source() { - return source; - } - - @Override - public boolean hasPermission(@NonNull String permission) { - return source.hasPermission(permission); - } - - @Override - public void sendMessage( - @NonNull Identity source, - @NonNull Component message, - @NonNull MessageType type) { - this.source.sendMessage(GsonComponentSerializer.gson().serialize(message)); - } - - @Override - public void sendMessage(TranslatableMessage message, Object... args) { - commandUtil.sendMessage(source(), locale(), message, args); - } - - @Override - public void disconnect(@NonNull Component reason) { - if (source instanceof ProxiedPlayer) { - ((ProxiedPlayer) source).disconnect(GsonComponentSerializer.gson().serialize(reason)); - } - } - - @Override - public void disconnect(TranslatableMessage message, Object... args) { - commandUtil.kickPlayer(source(), locale(), message, args); - } - - @Override - public @NonNull Audience audience() { - return this; - } - - public static final class BungeeConsoleAudience extends BungeeUserAudience - implements ConsoleAudience { - - public BungeeConsoleAudience(CommandSender source, CommandUtil commandUtil) { - super(new UUID(0, 0), "en_us", source, commandUtil); - } - - @Override - public void sendMessage( - @NonNull Identity source, - @NonNull Component message, - @NonNull MessageType type) { - source().sendMessage(BungeeComponentSerializer.get().serialize(message)); - } - } - - public static final class BungeePlayerAudience extends BungeeUserAudience - implements PlayerAudience { - - private final String username; - private final boolean online; - - public BungeePlayerAudience( - UUID uuid, - String username, - String locale, - CommandSender source, - boolean online, - CommandUtil commandUtil) { - super(uuid, locale, source, commandUtil); - this.username = username; - this.online = online; - } - - public BungeePlayerAudience( - UUID uuid, - String locale, - CommandSender source, - boolean online, - CommandUtil commandUtil) { - this(uuid, source.getName(), locale, source, online, commandUtil); - } - - @Override - public @NonNull String username() { - return username; - } - - @Override - public boolean online() { - return online; - } - } -} diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..572be29a --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 36457867..02fec9dd 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,22 +6,26 @@ plugins { dependencies { api(projects.api) + api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion) api("com.google.inject", "guice", Versions.guiceVersion) api("com.nukkitx.fastutil", "fastutil-short-object-maps", Versions.fastutilVersion) api("com.nukkitx.fastutil", "fastutil-int-object-maps", Versions.fastutilVersion) api("org.java-websocket", "Java-WebSocket", Versions.javaWebsocketVersion) - api("net.kyori", "adventure-api", Versions.adventureApiVersion) api("cloud.commandframework", "cloud-core", Versions.cloudVersion) api("org.yaml", "snakeyaml", Versions.snakeyamlVersion) + api("org.bstats", "bstats-base", Versions.bstatsVersion) } // present on all platforms provided("io.netty", "netty-transport", Versions.nettyVersion) provided("io.netty", "netty-codec", Versions.nettyVersion) +relocate("org.bstats") + configure { val constantsFile = "src/main/java/org/geysermc/floodgate/util/Constants.java" + replaceToken("\${floodgateVersion}", fullVersion(), constantsFile) replaceToken("\${branch}", branchName(), constantsFile) replaceToken("\${buildNumber}", buildNumber(), constantsFile) } diff --git a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java index bce37078..7b963d73 100644 --- a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java @@ -40,13 +40,14 @@ import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.packet.PacketHandlers; +import org.geysermc.floodgate.config.ConfigLoader; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfigHolder; -import org.geysermc.floodgate.config.loader.ConfigLoader; import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.module.ConfigLoadedModule; 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 { @@ -100,8 +101,6 @@ public class FloodgatePlatform { InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY); - // todo provide build number and branch for Geyser dump - guice.getInstance(NewsChecker.class).start(); } @@ -125,6 +124,8 @@ public class FloodgatePlatform { PrefixCheckTask.checkAndExecuteDelayed(config, logger); + guice.getInstance(Metrics.class); + return true; } @@ -139,6 +140,7 @@ public class FloodgatePlatform { } } + guice.getInstance(NewsChecker.class).shutdown(); api.getPlayerLink().stop(); return true; } diff --git a/core/src/main/java/org/geysermc/floodgate/addon/data/HandshakeDataImpl.java b/core/src/main/java/org/geysermc/floodgate/addon/data/HandshakeDataImpl.java index d0921f48..480175e9 100644 --- a/core/src/main/java/org/geysermc/floodgate/addon/data/HandshakeDataImpl.java +++ b/core/src/main/java/org/geysermc/floodgate/addon/data/HandshakeDataImpl.java @@ -70,7 +70,7 @@ public class HandshakeDataImpl implements HandshakeData { int usernameLength = Math.min(bedrockData.getUsername().length(), 16 - prefix.length()); javaUsername = prefix + bedrockData.getUsername().substring(0, usernameLength); if (config.isReplaceSpaces()) { - javaUsername = javaUsername.replaceAll(" ", "_"); + javaUsername = javaUsername.replace(" ", "_"); } javaUniqueId = Utils.getJavaUuid(bedrockData.getXuid()); diff --git a/core/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java index ab6d514a..fe8695fd 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java @@ -35,20 +35,20 @@ import cloud.commandframework.context.CommandContext; import com.google.inject.Inject; import lombok.Getter; import lombok.NoArgsConstructor; -import net.kyori.adventure.text.Component; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.link.LinkRequestResult; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.command.util.Permission; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.link.GlobalPlayerLinking; import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience; -import org.geysermc.floodgate.player.UserAudienceArgument; +import org.geysermc.floodgate.player.audience.ProfileAudience; +import org.geysermc.floodgate.player.audience.ProfileAudienceArgument; import org.geysermc.floodgate.util.Constants; -import org.geysermc.floodgate.util.Permissions; @NoArgsConstructor public final class LinkAccountCommand implements FloodgateCommand { @@ -60,8 +60,8 @@ public final class LinkAccountCommand implements FloodgateCommand { return commandManager.commandBuilder("linkaccount", ArgumentDescription.of("Link your Java account with your Bedrock account")) .senderType(PlayerAudience.class) - .permission(Permissions.COMMAND_LINK.get()) - .argument(UserAudienceArgument.of("player", true)) + .permission(Permission.COMMAND_LINK.get()) + .argument(ProfileAudienceArgument.of("player", true)) .argument(StringArgument.optional("code")) .handler(this::execute) .build(); @@ -90,6 +90,10 @@ public final class LinkAccountCommand implements FloodgateCommand { return; } + ProfileAudience targetUser = context.get("player"); + // allowUuid is false so username cannot be null + String targetName = targetUser.username(); + // when the player is a Bedrock player if (api.isFloodgatePlayer(sender.uuid())) { if (!context.contains("code")) { @@ -97,8 +101,6 @@ public final class LinkAccountCommand implements FloodgateCommand { return; } - UserAudience targetUser = context.get("player"); - String targetName = targetUser.username(); String code = context.get("code"); link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), code) @@ -125,7 +127,7 @@ public final class LinkAccountCommand implements FloodgateCommand { sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName); break; default: - sender.disconnect(Component.text("Invalid account linking result")); + sender.disconnect("Invalid account linking result"); break; } }); @@ -137,9 +139,6 @@ public final class LinkAccountCommand implements FloodgateCommand { return; } - UserAudience targetUser = context.get("player"); - String targetName = targetUser.username(); - link.createLinkRequest(sender.uuid(), sender.username(), targetName) .whenComplete((result, throwable) -> { if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) { diff --git a/core/src/main/java/org/geysermc/floodgate/command/TestCommand.java b/core/src/main/java/org/geysermc/floodgate/command/TestCommand.java index c5a1de80..f950da04 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/TestCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/TestCommand.java @@ -28,7 +28,6 @@ package org.geysermc.floodgate.command; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; import cloud.commandframework.context.CommandContext; -import net.kyori.adventure.text.Component; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.platform.command.FloodgateCommand; @@ -47,7 +46,7 @@ public class TestCommand implements FloodgateCommand { @Override public void execute(CommandContext context) { int players = FloodgateApi.getInstance().getPlayers().size(); - context.getSender().sendMessage(Component.text(players)); + context.getSender().sendMessage(String.valueOf(players)); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/core/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java index 0a76f243..1c5b0444 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java @@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience; import org.geysermc.floodgate.util.Constants; -import org.geysermc.floodgate.util.Permissions; +import org.geysermc.floodgate.command.util.Permission; @NoArgsConstructor public final class UnlinkAccountCommand implements FloodgateCommand { @@ -54,7 +54,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand { return commandManager.commandBuilder("unlinkaccount", ArgumentDescription.of("Unlink your Java account from your Bedrock account")) .senderType(PlayerAudience.class) - .permission(Permissions.COMMAND_UNLINK.get()) + .permission(Permission.COMMAND_UNLINK.get()) .handler(this::execute) .build(); } diff --git a/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java b/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java index bd106f47..45287f9e 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/WhitelistCommand.java @@ -38,17 +38,18 @@ import java.util.UUID; import lombok.Getter; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.command.util.Permission; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.TranslatableMessage; +import org.geysermc.floodgate.platform.util.PlayerType; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument; -import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; +import org.geysermc.floodgate.player.audience.ProfileAudience; +import org.geysermc.floodgate.player.audience.ProfileAudienceArgument; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.HttpUtils; -import org.geysermc.floodgate.util.Permissions; public class WhitelistCommand implements FloodgateCommand { @Inject private FloodgateConfig config; @@ -58,25 +59,25 @@ public class WhitelistCommand implements FloodgateCommand { public Command buildCommand(CommandManager commandManager) { Command.Builder builder = commandManager.commandBuilder("fwhitelist", ArgumentDescription.of("Easy way to whitelist Bedrock players")) - .permission(Permissions.COMMAND_WHITELIST.get()); + .permission(Permission.COMMAND_WHITELIST.get()); commandManager.command(builder .literal("add", "a") - .argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK)) + .argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK)) .handler(context -> performCommand(context, true))); return builder .literal("remove", "r") - .argument(UserAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK)) + .argument(ProfileAudienceArgument.of("player", true, true, PlayerType.ONLY_BEDROCK)) .handler(context -> performCommand(context, false)) .build(); } public void performCommand(CommandContext context, boolean add) { UserAudience sender = context.getSender(); - UserAudience player = context.get("player"); - UUID uuid = player.uuid(); - String name = player.username(); + ProfileAudience profile = context.get("player"); + UUID uuid = profile.uuid(); + String name = profile.username(); if (name == null && uuid == null) { sender.sendMessage(Message.UNEXPECTED_ERROR); diff --git a/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java b/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java index 49c70aaf..093289fe 100644 --- a/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java +++ b/core/src/main/java/org/geysermc/floodgate/command/main/MainCommand.java @@ -35,9 +35,9 @@ import cloud.commandframework.context.CommandContext; import java.util.Locale; import java.util.function.Consumer; import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.command.util.Permission; import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.util.Permissions; public final class MainCommand implements FloodgateCommand { @Override @@ -46,12 +46,13 @@ public final class MainCommand implements FloodgateCommand { "floodgate", ArgumentDescription.of("A set of Floodgate related actions in one command")) .senderType(UserAudience.class) - .permission(Permissions.COMMAND_MAIN.get()) + .permission(Permission.COMMAND_MAIN.get()) .handler(this::execute); for (SubCommand subCommand : SubCommand.VALUES) { commandManager.command(builder .literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description) + .permission(subCommand.permission.get()) .handler(subCommand.executor::accept) ); } @@ -65,10 +66,12 @@ public final class MainCommand implements FloodgateCommand { StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n"); for (SubCommand subCommand : SubCommand.VALUES) { - helpMessage.append('\n').append(COLOR_CHAR).append('b') - .append(subCommand.name().toLowerCase(Locale.ROOT)) - .append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7') - .append(subCommand.description); + if (context.getSender().hasPermission(subCommand.permission.get())) { + helpMessage.append('\n').append(COLOR_CHAR).append('b') + .append(subCommand.name().toLowerCase(Locale.ROOT)) + .append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7') + .append(subCommand.description); + } } context.getSender().sendMessage(helpMessage.toString()); @@ -77,11 +80,12 @@ public final class MainCommand implements FloodgateCommand { @RequiredArgsConstructor enum SubCommand { FIREWALL("Check if your outgoing firewall allows Floodgate to work properly", - FirewallCheckSubcommand::executeFirewall); + Permission.COMMAND_MAIN_FIREWALL, FirewallCheckSubcommand::executeFirewall); static final SubCommand[] VALUES = values(); final String description; + final Permission permission; final Consumer> executor; } } diff --git a/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java b/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java new file mode 100644 index 00000000..90f897fb --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/command/util/Permission.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +import static org.geysermc.floodgate.command.util.PermissionDefault.OP; +import static org.geysermc.floodgate.command.util.PermissionDefault.TRUE; + +public enum Permission { + COMMAND_MAIN("floodgate.command.floodgate", TRUE), + COMMAND_MAIN_FIREWALL(COMMAND_MAIN, "firewall", OP), + COMMAND_LINK("floodgate.command.linkaccount", TRUE), + COMMAND_UNLINK("floodgate.command.unlinkaccount", TRUE), + COMMAND_WHITELIST("floodgate.command.fwhitelist", OP), + + NEWS_RECEIVE("floodgate.news.receive", OP); + + private final String permission; + private final PermissionDefault defaultValue; + + Permission(String permission, PermissionDefault defaultValue) { + this.permission = permission; + this.defaultValue = defaultValue; + } + + Permission(Permission parent, String child, PermissionDefault defaultValue) { + this(parent.get() + "." + child, defaultValue); + } + + public String get() { + return permission; + } + + public PermissionDefault defaultValue() { + return defaultValue; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/command/util/PermissionDefault.java b/core/src/main/java/org/geysermc/floodgate/command/util/PermissionDefault.java new file mode 100644 index 00000000..a98e1e89 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/command/util/PermissionDefault.java @@ -0,0 +1,30 @@ +/* + * 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.util; + +public enum PermissionDefault { + TRUE, FALSE, OP, NOT_OP +} diff --git a/core/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java b/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java similarity index 58% rename from core/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java rename to core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java index d1e85689..43cbfb3e 100644 --- a/core/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java +++ b/core/src/main/java/org/geysermc/floodgate/config/ConfigLoader.java @@ -23,26 +23,27 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.config.loader; +package org.geysermc.floodgate.config; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.security.Key; +import java.util.UUID; +import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.configutils.ConfigUtilities; +import org.geysermc.configutils.file.codec.PathFileCodec; +import org.geysermc.configutils.file.template.ResourceTemplateReader; +import org.geysermc.configutils.updater.change.Changes; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.config.ProxyFloodgateConfig; -import org.geysermc.floodgate.config.updater.ConfigUpdater; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.KeyProducer; +@Getter @RequiredArgsConstructor public final class ConfigLoader { private final Path dataFolder; private final Class configClass; - private final DefaultConfigHandler configCreator; - private final ConfigUpdater updater; private final KeyProducer keyProducer; private final FloodgateCipher cipher; @@ -51,61 +52,42 @@ public final class ConfigLoader { @SuppressWarnings("unchecked") public T load() { - Path configPath = dataFolder.resolve("config.yml"); - - String defaultConfigName = "config.yml"; - boolean proxy = ProxyFloodgateConfig.class.isAssignableFrom(configClass); - if (proxy) { - defaultConfigName = "proxy-" + defaultConfigName; + String templateFile = "config.yml"; + if (ProxyFloodgateConfig.class.isAssignableFrom(configClass)) { + templateFile = "proxy-" + templateFile; } - boolean newConfig = !Files.exists(configPath); - if (newConfig) { - try { - configCreator.createDefaultConfig(defaultConfigName, configPath); - } catch (Exception exception) { - logger.error("Error while creating config", exception); - } - } + //todo old Floodgate logged a message when version = 0 and it generated a new key. + // Might be nice to allow you to run a function for a specific version. + + // it would also be nice to have sections in versionBuilder so that you don't have to + // provide the path all the time + + ConfigUtilities utilities = + ConfigUtilities.builder() + .fileCodec(PathFileCodec.of(dataFolder)) + .configFile("config.yml") + .templateReader(ResourceTemplateReader.of(getClass())) + .template(templateFile) + .changes(Changes.builder() + .version(1, Changes.versionBuilder() + .keyRenamed("player-link.enable", "player-link.enabled") + .keyRenamed("player-link.allow-linking", "player-link.allowed")) + .version(2, Changes.versionBuilder() + .keyRenamed("player-link.use-global-linking", "player-link.enable-global-linking")) + .build()) + .definePlaceholder("metrics.uuid", UUID::randomUUID) + .postInitializeCallbackArgument(this) + .build(); - T configInstance; try { - // check and update if the config is outdated - if (!newConfig) { - updater.update(this, defaultConfigName); - } - - FloodgateConfig config = ConfigInitializer.initializeFrom( - Files.newInputStream(configPath), configClass); - - try { - configInstance = (T) config; - } catch (ClassCastException exception) { - logger.error("Failed to cast config file to required class.", exception); - throw new RuntimeException(exception); - } - } catch (Exception exception) { - logger.error("Error while loading config", exception); + return (T) utilities.executeOn(configClass); + } catch (Throwable throwable) { throw new RuntimeException( - "Failed to load the config! Try to delete the config file", exception); + "Failed to load the config! Try to delete the config file if this error persists", + throwable + ); } - - Path keyPath = dataFolder.resolve(configInstance.getKeyFileName()); - // don't assume that the key always exists with the existence of a config - if (!Files.exists(keyPath)) { - generateKey(keyPath); - } - - try { - Key key = keyProducer.produceFrom(keyPath); - cipher.init(key); - configInstance.setKey(key); - } catch (IOException exception) { - logger.error("Error while reading the key", exception); - throw new RuntimeException("Failed to read the key!", exception); - } - - return configInstance; } public void generateKey(Path keyPath) { diff --git a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java b/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java index 2fa23c89..167c40f6 100644 --- a/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java +++ b/core/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java @@ -25,15 +25,20 @@ package org.geysermc.floodgate.config; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.Key; import lombok.Getter; +import org.geysermc.configutils.loader.callback.CallbackResult; +import org.geysermc.configutils.loader.callback.GenericPostInitializeCallback; /** * The global Floodgate configuration file used in every platform. Some platforms have their own * addition to the global configuration like {@link ProxyFloodgateConfig} for the proxies. */ @Getter -public class FloodgateConfig { +public class FloodgateConfig implements GenericPostInitializeCallback { private String keyFileName; private String usernamePrefix; private boolean replaceSpaces; @@ -42,22 +47,36 @@ public class FloodgateConfig { private DisconnectMessages disconnect; private PlayerLinkConfig playerLink; + private MetricsConfig metrics; private boolean debug; private int configVersion; private Key key; - public void setKey(Key key) { - if (this.key == null) { - this.key = key; - } - } - public boolean isProxy() { return this instanceof ProxyFloodgateConfig; } + @Override + public CallbackResult postInitialize(ConfigLoader loader) { + Path keyPath = loader.getDataFolder().resolve(getKeyFileName()); + + // don't assume that the key always exists with the existence of a config + if (!Files.exists(keyPath)) { + loader.generateKey(keyPath); + } + + try { + Key floodgateKey = loader.getKeyProducer().produceFrom(keyPath); + loader.getCipher().init(floodgateKey); + key = floodgateKey; + } catch (IOException exception) { + return CallbackResult.failed(exception.getMessage()); + } + return CallbackResult.ok(); + } + @Getter public static class DisconnectMessages { private String invalidKey; @@ -68,10 +87,16 @@ public class FloodgateConfig { public static class PlayerLinkConfig { private boolean enabled; private boolean requireLink; - private boolean enableOwnLinking = false; + private boolean enableOwnLinking; private boolean allowed; private long linkCodeTimeout; private String type; private boolean enableGlobalLinking; } + + @Getter + public static class MetricsConfig { + private boolean enabled; + private String uuid; + } } diff --git a/core/src/main/java/org/geysermc/floodgate/config/loader/ConfigInitializer.java b/core/src/main/java/org/geysermc/floodgate/config/loader/ConfigInitializer.java deleted file mode 100644 index dfa49a3d..00000000 --- a/core/src/main/java/org/geysermc/floodgate/config/loader/ConfigInitializer.java +++ /dev/null @@ -1,109 +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.config.loader; - -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.LinkedHashMap; -import java.util.Map; -import org.geysermc.floodgate.config.FloodgateConfig; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; -import org.yaml.snakeyaml.introspector.BeanAccess; -import org.yaml.snakeyaml.introspector.FieldProperty; -import org.yaml.snakeyaml.introspector.Property; -import org.yaml.snakeyaml.introspector.PropertyUtils; - -public class ConfigInitializer { - private static final Yaml YAML; - - static { - Constructor constructor = - new CustomClassLoaderConstructor(ConfigInitializer.class.getClassLoader()); - - constructor.setPropertyUtils(new PropertyUtils() { - @Override - protected Map getPropertiesMap(Class type, BeanAccess bAccess) { - Map properties = new LinkedHashMap<>(); - getPropertiesFromClass(type, FloodgateConfig.class, properties); - return properties; - } - - private void getPropertiesFromClass( - Class type, - Class stopAfter, - Map propertyMap) { - - Class current = type; - while (!Object.class.equals(current)) { - for (Field field : current.getDeclaredFields()) { - int modifiers = field.getModifiers(); - if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { - String correctName = getCorrectName(field.getName()); - // children should override parents - propertyMap.putIfAbsent(correctName, new FieldProperty(field)); - } - - if (field.getClass().getSuperclass().equals(current)) { - getPropertiesFromClass(field.getClass(), field.getClass(), propertyMap); - } - } - - if (current.equals(stopAfter)) { - return; - } - - current = type.getSuperclass(); - } - } - - private String getCorrectName(String name) { - // convert sendFloodgateData to send-floodgate-data, - // which is the style of writing config fields - StringBuilder propertyBuilder = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { - char current = name.charAt(i); - if (Character.isUpperCase(current)) { - propertyBuilder.append('-').append(Character.toLowerCase(current)); - } else { - propertyBuilder.append(current); - } - } - return propertyBuilder.toString(); - } - }); - constructor.getPropertyUtils().setSkipMissingProperties(true); - YAML = new Yaml(constructor); - } - - public static T initializeFrom( - InputStream dataStream, - Class configClass) { - return YAML.loadAs(dataStream, configClass); - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/config/loader/DefaultConfigHandler.java b/core/src/main/java/org/geysermc/floodgate/config/loader/DefaultConfigHandler.java deleted file mode 100644 index 4549237b..00000000 --- a/core/src/main/java/org/geysermc/floodgate/config/loader/DefaultConfigHandler.java +++ /dev/null @@ -1,159 +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.config.loader; - -import static org.geysermc.floodgate.util.MessageFormatter.format; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import org.geysermc.floodgate.util.Utils; - -public class DefaultConfigHandler { - public void createDefaultConfig(String defaultConfigLocation, Path configPath) throws IOException { - List configLines = loadDefaultConfig(defaultConfigLocation); - - // writing the new config file - Files.write(configPath, configLines); - } - - public List loadDefaultConfig(String defaultConfigLocation) - throws IOException { - List lines = Utils.readAllLines(defaultConfigLocation); - - List configLines = new ArrayList<>(); - String parentConfig = null; - List parentLines = null; - - int lastInsertLine = -1; - int tempAddAfter = -1; - - for (String line : lines) { - // >>(space) or >>| - if (line.startsWith(">>")) { - if (line.length() >= 3) { - - // define parent file - if (line.charAt(2) == ' ') { - if (tempAddAfter != -1) { - throw new IllegalStateException( - "Cannot define new parent without closing the current section"); - } - parentConfig = line.substring(3); - parentLines = null; - lastInsertLine = -1; - continue; - } - - // define start / end of insert section - if (line.charAt(2) == '|') { - // end section - if (line.length() == 3) { - if (tempAddAfter == -1) { - throw new IllegalStateException("Cannot close an unclosed section"); - } - lastInsertLine = tempAddAfter; - tempAddAfter = -1; - continue; - } - - // start insert section - if (parentConfig == null) { - throw new IllegalStateException( - "Cannot start insert section without providing a parent"); - } - - if (tempAddAfter != -1) { - throw new IllegalStateException( - "Cannot start section with an unclosed section"); - } - - // note that addAfter starts counting from 1 - int addAfter = Integer.parseInt(line.substring(4)) - 1; - if (lastInsertLine > -1 && addAfter < lastInsertLine) { - throw new IllegalStateException(format( - "Cannot add the same lines twice {} {}", - addAfter, lastInsertLine - )); - } - - // as you can see by this implementation - // we don't support parent files in parent files - - if (lastInsertLine == -1) { - parentLines = Utils.readAllLines(parentConfig); - - for (int i = 0; i <= addAfter; i++) { - configLines.add(parentLines.get(i)); - } - } else { - for (int i = lastInsertLine; i <= addAfter; i++) { - configLines.add(parentLines.get(i)); - } - } - - tempAddAfter = addAfter; - continue; - } - - if (line.charAt(2) == '*') { - if (parentConfig == null) { - throw new IllegalStateException( - "Cannot write rest of the parent without providing a parent"); - } - - if (tempAddAfter != -1) { - throw new IllegalStateException( - "Cannot write rest of the parent config while an insert section is still open"); - } - - if (lastInsertLine == -1) { - parentLines = Utils.readAllLines(parentConfig); - configLines.addAll(parentLines); - continue; - } - - // the lastInsertLine has already been printed, so we won't print it twice - for (int i = lastInsertLine + 1; i < parentLines.size(); i++) { - configLines.add(parentLines.get(i)); - } - continue; - } - - throw new IllegalStateException( - "The use of >>" + line.charAt(2) + " is unknown"); - } - throw new IllegalStateException("Unable do something with just >>"); - } - // everything else: comments and key/value lines will be added - configLines.add(line); - } - - return configLines; - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java b/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java deleted file mode 100644 index f2877c85..00000000 --- a/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java +++ /dev/null @@ -1,182 +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.config.updater; - -import com.google.common.base.Ascii; -import com.google.inject.Inject; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.config.loader.DefaultConfigHandler; - -public final class ConfigFileUpdater { - @Inject private FloodgateLogger logger; - @Inject private DefaultConfigHandler defaultConfigHandler; - - /** - * Simple config file updater. Please note that all the keys should be unique and that this - * system wasn't made for complex configurations. - * - * @param configLocation the location of the Floodgate config - * @param currentVersion the key value map of the current config - * @param renames name changes introduced in this version. new (key) to old - * (value) - * @param defaultConfigLocation the location of the default Floodgate config - * @throws IOException if an I/O error occurs - */ - public void update( - Path configLocation, - Map currentVersion, - Map renames, - String defaultConfigLocation) - throws IOException { - - List notFound = new ArrayList<>(); - List newConfig = defaultConfigHandler.loadDefaultConfig(defaultConfigLocation); - - String spaces = ""; - Map map = null; - - String line; - for (int i = 0; i < newConfig.size(); i++) { - line = newConfig.get(i); - // we don't have to check comments or empty lines - if (line.isEmpty() || line.charAt(0) == '#') { - continue; - } - - StringBuilder currentSpaces = new StringBuilder(); - while (line.charAt(currentSpaces.length()) == Ascii.SPACE) { - currentSpaces.append(Ascii.SPACE); - } - - // end of subcategory - if (!spaces.isEmpty() && currentSpaces.length() < spaces.length()) { - // we can assume this since we don't allow subcategories of subcategories - spaces = ""; - map = null; - } - - // ignore comments - if (line.charAt(currentSpaces.length()) == '#') { - continue; - } - - int splitIndex = line.indexOf(':'); - // if the line has a 'key: value' structure - if (splitIndex != -1) { - - // start of a subcategory - if (line.length() == splitIndex + 1) { - if (currentSpaces.length() > 0) { - throw new IllegalStateException( - "Config too complex! I can't understand subcategories of a subcategory"); - } - - spaces = " "; - //todo allow rename of subcategory? - //noinspection unchecked - map = (Map) currentVersion.get(line.substring(0, splitIndex)); - continue; - } - - String name = line.substring(spaces.length(), splitIndex); - - // don't change the config-version to the old value! - if (name.equals("config-version")) { - continue; - } - - // allow multiple renames - String tempName; - String oldName = name; - do { - tempName = oldName; - oldName = renames.getOrDefault(oldName, oldName); - } while (!oldName.equals(tempName)); - - Object value; - if (map != null) { - value = map.get(oldName); - } else { - value = currentVersion.get(spaces + oldName); - } - - // use default value if the key doesn't exist in the current version - if (value == null) { - notFound.add(name); - continue; - } - - if (value instanceof String) { - String v = (String) value; - if (!v.startsWith("\"") || !v.endsWith("\"")) { - value = "\"" + value + "\""; - } - //todo this doesn't update {0} {1} to {} {} e.g. - } - - logger.debug(name + " has been changed to " + value); - newConfig.set(i, spaces + name + ": " + value); - } - } - - Files.deleteIfExists(configLocation.getParent().resolve("config-old.yml")); - Files.copy(configLocation, configLocation.getParent().resolve("config-old.yml")); - Files.write(configLocation, newConfig); - - logger.info("Successfully updated the config file! " + - "Your old config has been moved to config-old.yml"); - - if (!notFound.isEmpty()) { - StringBuilder messageBuilder = new StringBuilder( - "Please note that the following keys we not found in the old config and " + - "are now using the default Floodgate config value. Missing/new keys: "); - - boolean first = true; - for (String value : notFound) { - if (!first) { - messageBuilder.append(", "); - } - - String renamed = renames.get(value); - if (renamed != null) { - messageBuilder.append(renamed).append(" to "); - } - - messageBuilder.append(value); - - first = false; - } - - logger.info(messageBuilder.toString()); - } - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java b/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java deleted file mode 100644 index c62aaab1..00000000 --- a/core/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java +++ /dev/null @@ -1,122 +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.config.updater; - -import static com.google.common.base.Preconditions.checkArgument; -import static org.geysermc.floodgate.util.MessageFormatter.format; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.config.loader.ConfigLoader; -import org.yaml.snakeyaml.Yaml; - -@RequiredArgsConstructor -public final class ConfigUpdater { - private static final int CONFIG_VERSION = 2; - private final Path dataFolder; - private final ConfigFileUpdater fileUpdater; - private final FloodgateLogger logger; - - public void update(ConfigLoader loader, String defaultConfigLocation) { - Path configLocation = dataFolder.resolve("config.yml"); - - Map config; - - try (BufferedReader configReader = Files.newBufferedReader(configLocation)) { - config = new Yaml().load(configReader); - } catch (IOException exception) { - logger.error("Error while opening the config file", exception); - throw new RuntimeException("Failed to update config", exception); - } - - // new name -> old name - Map renames = new HashMap<>(); - - int version = 0; // pre-rewrite is the default config version - - Object versionElement = config.get("config-version"); - // only rewrite configs have a config-version - if (versionElement == null) { - logger.warn("We've detected a pre-rewrite config file, please note that Floodgate " + - "doesn't not work properly if you don't update your Floodgate key used on " + - "all your servers (including Geyser). We'll try to update your Floodgate " + - "config now and we'll also generate a new Floodgate key for you, but if " + - "you're running a network or if you're running a Spigot server with " + - "Geyser Standalone please update as you'll no longer be able to connect."); - renames.put("enabled", "enable"); //todo make dump system and add a boolean 'found-legacy-key' or something like that - renames.put("allowed", "allow-linking"); - - // relocate the old key so that they can restore it if it was a new key - Path keyFilePath = dataFolder.resolve((String) config.get("key-file-name")); - if (Files.exists(keyFilePath)) { - try { - Files.copy(keyFilePath, dataFolder.resolve("old-key.pem")); - } catch (IOException exception) { - throw new RuntimeException( - "Failed to relocate the old key to make place for a new key", - exception); - } - } - loader.generateKey(keyFilePath); - } else { - // get (and verify) the config version - checkArgument( - versionElement instanceof Integer, - "Config version should be an integer. Did someone mess with the config?" - ); - - version = (int) versionElement; - checkArgument( - version > 0 && version <= CONFIG_VERSION, - format("Config is newer then possible on this version! Expected {}, got {}", - CONFIG_VERSION, version)); - } - - // config is already up-to-date - if (version == CONFIG_VERSION) { - return; - } - - if (version < 2) { - // renamed 'use-global-linking' to 'enable-global-linking' - // and added 'enable-own-linking' - renames.put("enable-global-linking", "use-global-linking"); - } - - try { - fileUpdater.update(configLocation, config, renames, defaultConfigLocation); - } catch (IOException exception) { - logger.error("Error while updating the config file", exception); - throw new RuntimeException("Failed to update config", exception); - } - } -} diff --git a/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java b/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java index 48449372..9025ca0d 100644 --- a/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java +++ b/core/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java @@ -77,7 +77,7 @@ public abstract class CommonPlatformInjector implements PlatformInjector { } /** - * Method to loop throguh all the addons and call {@link InjectorAddon#onChannelClosed(Channel)} + * Method to loop through all the addons and call {@link InjectorAddon#onChannelClosed(Channel)} * if {@link InjectorAddon#shouldInject()} * * @param channel the channel that was injected diff --git a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java index ad854b13..f075bc22 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -40,12 +40,9 @@ import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ConfigLoader; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfigHolder; -import org.geysermc.floodgate.config.loader.ConfigLoader; -import org.geysermc.floodgate.config.loader.DefaultConfigHandler; -import org.geysermc.floodgate.config.updater.ConfigFileUpdater; -import org.geysermc.floodgate.config.updater.ConfigUpdater; import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.Base64Topping; @@ -105,27 +102,10 @@ public class CommonModule extends AbstractModule { @Singleton public ConfigLoader configLoader( @Named("configClass") Class configClass, - DefaultConfigHandler defaultConfigHandler, - ConfigUpdater configUpdater, KeyProducer producer, FloodgateCipher cipher, FloodgateLogger logger) { - return new ConfigLoader(dataDirectory, configClass, defaultConfigHandler, configUpdater, - producer, cipher, logger); - } - - @Provides - @Singleton - public DefaultConfigHandler defaultConfigCreator() { - return new DefaultConfigHandler(); - } - - @Provides - @Singleton - public ConfigUpdater configUpdater( - ConfigFileUpdater configFileUpdater, - FloodgateLogger logger) { - return new ConfigUpdater(dataDirectory, configFileUpdater, logger); + return new ConfigLoader(dataDirectory, configClass, producer, cipher, logger); } @Provides diff --git a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java index 38918dbc..901321c3 100644 --- a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java +++ b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java @@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.HttpUtils; import org.geysermc.floodgate.util.HttpUtils.HttpResponse; -import org.geysermc.floodgate.util.Permissions; +import org.geysermc.floodgate.command.util.Permission; public class NewsChecker { private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); @@ -72,8 +72,6 @@ public class NewsChecker { } private void checkNews() { - // todo also check news for the downloaded database types - HttpResponse response = HttpUtils.getSilent( Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME, @@ -121,14 +119,14 @@ public class NewsChecker { return; } - if (commandUtil.hasPermission(player, Permissions.NEWS_RECEIVE.get())) { + if (commandUtil.hasPermission(player, Permission.NEWS_RECEIVE.get())) { String message = Constants.COLOR_CHAR + "a " + news.getMessage(); commandUtil.sendMessage(player, message); } break; case BROADCAST_TO_OPERATORS: Collection onlinePlayers = commandUtil.getOnlinePlayersWithPermission( - Permissions.NEWS_RECEIVE.get() + Permission.NEWS_RECEIVE.get() ); for (Object onlinePlayer : onlinePlayers) { @@ -182,7 +180,8 @@ public class NewsChecker { schedule(delayMs > 0 ? delayMs : 0); break; case CONFIG_SPECIFIC: - //todo + //todo this can replace the downloaded database types update check. + // check if ConfigUtils has a way to check this easily break; } activeNewsItems.put(item.getId(), item); diff --git a/core/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java b/core/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java index ad4c2764..d159a29e 100644 --- a/core/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java +++ b/core/src/main/java/org/geysermc/floodgate/platform/command/CommandUtil.java @@ -25,30 +25,118 @@ package org.geysermc.floodgate.platform.command; +import static org.geysermc.floodgate.platform.util.PlayerType.ALL_PLAYERS; +import static org.geysermc.floodgate.platform.util.PlayerType.ONLY_BEDROCK; + +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.Objects; import java.util.UUID; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.platform.util.PlayerType; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; +import org.geysermc.floodgate.player.audience.ProfileAudience; +import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.Utils; /** * An interface used across all Floodgate platforms to simple stuff in commands like kicking players * and sending player messages independent of the Floodgate platform implementation. */ -public interface CommandUtil { - @NonNull UserAudience getAudience(final @NonNull Object source); +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class CommandUtil { + protected final LanguageManager manager; + protected final FloodgateApi api; - @Nullable UserAudience getAudienceByUuid(final @NonNull UUID uuid); + public abstract @NonNull UserAudience getUserAudience(@NonNull Object source); - @NonNull UserAudience getOfflineAudienceByUuid(final @NonNull UUID uuid); + /** + * Get a ProfileAudience from a source. The source should be a platform-specific player instance + * when the player is online, and the username / uuid of the requested player when offline. + * + * @param source source to create a ProfileAudience from + * @param allowOffline if offline players are allowed + * @return a ProfileAudience unless allowOffline is false and the player isn't online + */ + public @Nullable ProfileAudience getProfileAudience( + @NonNull Object source, + boolean allowOffline) { + Objects.requireNonNull(source); - @Nullable UserAudience getAudienceByUsername(final @NonNull String username); + if (source instanceof UUID) { + return allowOffline ? new ProfileAudience((UUID) source, null) : null; + } else if (source instanceof String) { + return allowOffline ? new ProfileAudience(null, (String) source) : null; + } else { + return new ProfileAudience(getUuidFromSource(source), getUsernameFromSource(source)); + } + } - @NonNull UserAudience getOfflineAudienceByUsername(final @NonNull String username); + protected abstract String getUsernameFromSource(@NonNull Object source); + protected abstract UUID getUuidFromSource(@NonNull Object source); - @NonNull Collection getOnlineUsernames(final @NonNull PlayerType limitTo); + protected abstract Collection getOnlinePlayers(); + + public @NonNull Collection getOnlineUsernames(@NonNull PlayerType limitTo) { + Collection players = getOnlinePlayers(); + + Collection usernames = new ArrayList<>(); + switch (limitTo) { + case ALL_PLAYERS: + for (Object player : players) { + usernames.add(getUsernameFromSource(player)); + } + break; + case ONLY_JAVA: + for (Object player : players) { + if (!api.isFloodgatePlayer(getUuidFromSource(player))) { + usernames.add(getUsernameFromSource(player)); + } + } + break; + case ONLY_BEDROCK: + for (Object player : players) { + if (api.isFloodgatePlayer(getUuidFromSource(player))) { + usernames.add(getUsernameFromSource(player)); + } + } + break; + default: + throw new IllegalStateException("Unknown PlayerType"); + } + return usernames; + } + + /** + * + * @param uuid + * @return + */ + public abstract Object getPlayerByUuid(@NonNull UUID uuid); + + public Object getPlayerByUuid(@NonNull UUID uuid, PlayerType limitTo) { + return applyPlayerTypeFilter(getPlayerByUuid(uuid), limitTo, uuid); + } + + public abstract Object getPlayerByUsername(@NonNull String username); + + public Object getPlayerByUsername(@NonNull String username, PlayerType limitTo) { + return applyPlayerTypeFilter(getPlayerByUsername(username), limitTo, username); + } + + protected Object applyPlayerTypeFilter(Object player, PlayerType filter, Object fallback) { + if (filter == ALL_PLAYERS || player instanceof String || player instanceof UUID) { + return player; + } + return (filter == ONLY_BEDROCK) == api.isFloodgatePlayer(getUuidFromSource(player)) + ? player + : fallback; + } /** * Checks if the given player has the given permission. @@ -57,7 +145,7 @@ public interface CommandUtil { * @param permission the permission to check * @return true or false depending on if the player has the permission */ - boolean hasPermission(Object player, String permission); + public abstract boolean hasPermission(Object player, String permission); /** * Get all online players with the given permission. @@ -65,17 +153,15 @@ public interface CommandUtil { * @param permission the permission to check * @return a list of online players that have the given permission */ - Collection getOnlinePlayersWithPermission(String permission); - - /** - * Send a message to the specified target, no matter what platform Floodgate is running on. - * - * @param target the player that should receive the message - * @param message the command message - * @param locale the locale of the player - * @param args the arguments - */ - void sendMessage(Object target, String locale, TranslatableMessage message, Object... args); + public Collection getOnlinePlayersWithPermission(String permission) { + List players = new ArrayList<>(); + for (Object player : getOnlinePlayers()) { + if (hasPermission(player, permission)) { + players.add(player); + } + } + return players; + } /** * Sends a raw message to the specified target, no matter what platform Floodgate is running @@ -84,18 +170,19 @@ public interface CommandUtil { * @param target the player that should receive the message * @param message the message */ - void sendMessage(Object target, String message); + public abstract void sendMessage(Object target, String message); /** - * Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} - * except it kicks the player using the given message as the kick reason. + * Kicks the given player using the given message as the kick reason. * * @param player the player that should be kicked * @param message the command message - * @param locale the locale of the player - * @param args the arguments */ - void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args); + public abstract void kickPlayer(Object player, String message); + + public String translateMessage(String locale, TranslatableMessage message, Object... args) { + return message.translateMessage(manager, locale, args); + } /** * Whitelist the given Bedrock player. @@ -105,7 +192,7 @@ public interface CommandUtil { * @return true if the player has been whitelisted, false if the player was already whitelisted. * Defaults to false when this platform doesn't support whitelisting. */ - default boolean whitelistPlayer(String xuid, String username) { + public boolean whitelistPlayer(String xuid, String username) { UUID uuid = Utils.getJavaUuid(xuid); return whitelistPlayer(uuid, username); } @@ -118,7 +205,7 @@ public interface CommandUtil { * @return true if the player has been whitelisted, false if the player was already whitelisted. * Defaults to false when this platform doesn't support whitelisting. */ - default boolean whitelistPlayer(UUID uuid, String username) { + public boolean whitelistPlayer(UUID uuid, String username) { return false; } @@ -130,7 +217,7 @@ public interface CommandUtil { * @return true if the player has been removed from the whitelist, false if the player wasn't * whitelisted. Defaults to false when this platform doesn't support whitelisting. */ - default boolean removePlayerFromWhitelist(String xuid, String username) { + public boolean removePlayerFromWhitelist(String xuid, String username) { UUID uuid = Utils.getJavaUuid(xuid); return removePlayerFromWhitelist(uuid, username); } @@ -143,7 +230,7 @@ public interface CommandUtil { * @return true if the player has been removed from the whitelist, false if the player wasn't * whitelisted. Defaults to false when this platform doesn't support whitelisting. */ - default boolean removePlayerFromWhitelist(UUID uuid, String username) { + public boolean removePlayerFromWhitelist(UUID uuid, String username) { return false; } } diff --git a/core/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java b/core/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java index 306ed564..ee51f498 100644 --- a/core/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java +++ b/core/src/main/java/org/geysermc/floodgate/platform/command/TranslatableMessage.java @@ -28,8 +28,8 @@ package org.geysermc.floodgate.platform.command; import org.geysermc.floodgate.util.LanguageManager; /** - * TranslatableMessage is the interface for a message that can be translated. - * Messages are generally implemented using enums. + * TranslatableMessage is the interface for a message that can be translated. Messages are generally + * implemented using enums. */ public interface TranslatableMessage { /** @@ -52,7 +52,7 @@ public interface TranslatableMessage { for (int i = 0; i < translateParts.length; i++) { builder.append(manager.getString(translateParts[i], locale, args)); if (translateParts.length != i + 1) { - builder.append(" "); + builder.append(' '); } } return builder.toString(); diff --git a/core/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java b/core/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java index 4807c017..d9901848 100644 --- a/core/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/platform/util/PlatformUtils.java @@ -25,37 +25,26 @@ package org.geysermc.floodgate.platform.util; -import java.util.Collection; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; +import lombok.RequiredArgsConstructor; -public interface PlatformUtils { +@RequiredArgsConstructor +public abstract class PlatformUtils { /** - * Send a message to the specified player, no matter what platform Floodgate is running on. - * - * @param player the player to send the message to - * @param message the command message - * @param locale the locale of the player - * @param args the arguments + * Returns the authentication type used on the platform */ - void sendMessage(Object player, String locale, TranslatableMessage message, Object... args); + public abstract AuthType authType(); /** - * Same as {@link CommandUtil#sendMessage(Object, String, TranslatableMessage, Object...)} except it - * kicks the player. - * - * @param player the player to send the message to - * @param message the command message - * @param locale the locale of the player - * @param args the arguments + * Returns the Minecraft version the server is based on (or the most recent supported version + * for proxy platforms) */ - void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args); + public abstract String minecraftVersion(); - Collection getOnlineUsernames(PlayerType limitTo); + public abstract String serverImplementationName(); - enum PlayerType { - ALL_PLAYERS, - ONLY_BEDROCK, - ONLY_JAVA + public enum AuthType { + ONLINE, + PROXIED, + OFFLINE } } diff --git a/core/src/main/java/org/geysermc/floodgate/platform/util/PlayerType.java b/core/src/main/java/org/geysermc/floodgate/platform/util/PlayerType.java new file mode 100644 index 00000000..f8c0c97c --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/platform/util/PlayerType.java @@ -0,0 +1,32 @@ +/* + * 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.util; + +public enum PlayerType { + ALL_PLAYERS, + ONLY_BEDROCK, + ONLY_JAVA +} diff --git a/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java b/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java index f112c26e..950d999c 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/player/FloodgateHandshakeHandler.java @@ -35,7 +35,6 @@ import io.netty.util.AttributeKey; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import java.net.InetSocketAddress; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import lombok.AccessLevel; @@ -96,7 +95,7 @@ public final class FloodgateHandshakeHandler { String floodgateData = null; int dataVersion = -1; - StringBuilder hostnameBuilder = new StringBuilder(); + StringBuilder builder = new StringBuilder(); for (String value : hostnameItems) { int version = FloodgateCipher.version(value); if (floodgateData == null && version != -1) { @@ -104,10 +103,14 @@ public final class FloodgateHandshakeHandler { dataVersion = version; continue; } - hostnameBuilder.append(value).append('\0'); + + if (builder.length() > 0) { + builder.append('\0'); + } + builder.append(value); } - // hostname now doesn't have Floodgate data anymore if it had - return new HostnameSeparationResult(floodgateData, dataVersion, hostnameBuilder.toString()); + // the new hostname doesn't have Floodgate data anymore, if it had Floodgate data. + return new HostnameSeparationResult(floodgateData, dataVersion, builder.toString()); } public CompletableFuture handle( @@ -218,8 +221,6 @@ public final class FloodgateHandshakeHandler { bedrockData.getVerifyCode()); } - correctHostname(handshakeData); - FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, handshakeData); api.addPlayer(player); @@ -247,30 +248,9 @@ public final class FloodgateHandshakeHandler { bedrockData, configHolder.get(), null, hostname); handshakeHandlers.callHandshakeHandlers(handshakeData); - if (bedrockData != null) { - correctHostname(handshakeData); - } - return new HandshakeResult(resultType, handshakeData, bedrockData, null); } - private void correctHostname(HandshakeData handshakeData) { - BedrockData bedrockData = handshakeData.getBedrockData(); - UUID correctUuid = handshakeData.getCorrectUniqueId(); - - // replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid - String[] split = handshakeData.getHostname().split("\0"); - if (split.length >= 3) { - if (logger.isDebug()) { - logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'", - split[1], bedrockData.getIp(), split[2], correctUuid.toString()); - } - split[1] = bedrockData.getIp(); - split[2] = correctUuid.toString(); - } - handshakeData.setHostname(String.join("\0", split)); - } - private CompletableFuture> fetchLinkedPlayer(BedrockData data) { if (!api.getPlayerLink().isEnabled()) { return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null)); diff --git a/core/src/main/java/org/geysermc/floodgate/player/UserAudience.java b/core/src/main/java/org/geysermc/floodgate/player/UserAudience.java index 6a8d998b..9ed5e20d 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/UserAudience.java +++ b/core/src/main/java/org/geysermc/floodgate/player/UserAudience.java @@ -25,52 +25,80 @@ package org.geysermc.floodgate.player; +import java.util.Objects; import java.util.UUID; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.audience.MessageType; -import net.kyori.adventure.identity.Identified; -import net.kyori.adventure.identity.Identity; -import net.kyori.adventure.text.Component; +import lombok.Getter; +import lombok.experimental.Accessors; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.TranslatableMessage; -public interface UserAudience extends Identified, Identity, Audience { - @Override - @NonNull UUID uuid(); +@Getter @Accessors(fluent = true) +public class UserAudience { + private final @NonNull UUID uuid; + private final @NonNull String username; + private final @NonNull String locale; + private final @NonNull Object source; + private final @NonNull CommandUtil commandUtil; - @NonNull String username(); - - @NonNull String locale(); - - @NonNull Object source(); - - boolean hasPermission(@NonNull final String permission); - - @Override - void sendMessage(final @NonNull Identity source, - final @NonNull Component message, - final @NonNull MessageType type); - - void sendMessage(TranslatableMessage message, Object... args); - - default void sendMessage(String message) { - sendMessage(Component.text(message)); + public UserAudience( + @NonNull UUID uuid, + @NonNull String username, + @NonNull String locale, + @NonNull Object source, + @NonNull CommandUtil commandUtil) { + this.uuid = Objects.requireNonNull(uuid); + this.username = username; + this.locale = Objects.requireNonNull(locale); + this.source = Objects.requireNonNull(source); + this.commandUtil = Objects.requireNonNull(commandUtil); } - void disconnect(@NonNull final Component reason); - - void disconnect(TranslatableMessage message, Object... args); - - @Override - default @NonNull Identity identity() { - return this; + public boolean hasPermission(@NonNull String permission) { + return commandUtil.hasPermission(source(), permission); } - interface PlayerAudience extends UserAudience { - boolean online(); + public void sendMessage(String message) { + commandUtil.sendMessage(source(), message); } - interface ConsoleAudience extends UserAudience { + public void sendMessage(TranslatableMessage message, Object... args) { + sendMessage(translateMessage(message, args)); + } + public void disconnect(@NonNull String reason) { + commandUtil.kickPlayer(source(), reason); + } + + public void disconnect(TranslatableMessage message, Object... args) { + disconnect(translateMessage(message, args)); + } + + public String translateMessage(TranslatableMessage message, Object... args) { + return commandUtil.translateMessage(locale(), message, args); + } + + @Getter @Accessors(fluent = true) + public static class PlayerAudience extends UserAudience { + private final boolean online; + + public PlayerAudience( + @NonNull UUID uuid, + @NonNull String username, + @NonNull String locale, + @NonNull Object source, + @NonNull CommandUtil commandUtil, + boolean online) { + super(uuid, username, locale, source, commandUtil); + + this.online = online; + } + } + + @Getter @Accessors(fluent = true) + public static class ConsoleAudience extends UserAudience { + public ConsoleAudience(@NonNull Object source, @NonNull CommandUtil commandUtil) { + super(new UUID(0, 0), "CONSOLE", "en_us", source, commandUtil); + } } } diff --git a/core/src/main/java/org/geysermc/floodgate/util/Permissions.java b/core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudience.java similarity index 71% rename from core/src/main/java/org/geysermc/floodgate/util/Permissions.java rename to core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudience.java index 2f51f1f9..299f8152 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Permissions.java +++ b/core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudience.java @@ -23,23 +23,20 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.util; +package org.geysermc.floodgate.player.audience; -public enum Permissions { - COMMAND_MAIN("floodgate.command.floodgate"), - COMMAND_LINK("floodgate.command.linkaccount"), - COMMAND_UNLINK("floodgate.command.unlinkaccount"), - COMMAND_WHITELIST("floodgate.command.fwhitelist"), +import java.util.UUID; +import lombok.Getter; +import lombok.experimental.Accessors; +import org.checkerframework.checker.nullness.qual.Nullable; - NEWS_RECEIVE("floodgate.news.receive"); +@Getter @Accessors(fluent = true) +public final class ProfileAudience { + private final @Nullable UUID uuid; + private final @Nullable String username; - private final String permission; - - Permissions(String permission) { - this.permission = permission; - } - - public String get() { - return permission; + public ProfileAudience(@Nullable UUID uuid, @Nullable String username) { + this.uuid = uuid; + this.username = username; } } diff --git a/core/src/main/java/org/geysermc/floodgate/player/UserAudienceArgument.java b/core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudienceArgument.java similarity index 67% rename from core/src/main/java/org/geysermc/floodgate/player/UserAudienceArgument.java rename to core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudienceArgument.java index 6bebeb3b..9c425637 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/UserAudienceArgument.java +++ b/core/src/main/java/org/geysermc/floodgate/player/audience/ProfileAudienceArgument.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.player; +package org.geysermc.floodgate.player.audience; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; @@ -37,62 +37,58 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.platform.command.CommandUtil; +import org.geysermc.floodgate.platform.util.PlayerType; +import org.geysermc.floodgate.player.UserAudience; -public final class UserAudienceArgument extends CommandArgument { - private UserAudienceArgument(final @NonNull String name, final UserAudienceParser parser) { - super(true, name, parser, UserAudience.class); +public class ProfileAudienceArgument extends CommandArgument { + private ProfileAudienceArgument(@NonNull String name, ProfileAudienceParser parser) { + super(true, name, parser, ProfileAudience.class); } - public static UserAudienceArgument of( - final String name, - final boolean allowUuid, - final boolean allowOffline, - final PlayerType limitTo) { - return new UserAudienceArgument(name, - new UserAudienceParser(allowUuid, allowOffline, limitTo)); + public static ProfileAudienceArgument of( + String name, + boolean allowUuid, + boolean allowOffline, + PlayerType limitTo) { + return new ProfileAudienceArgument(name, + new ProfileAudienceParser(allowUuid, allowOffline, limitTo)); } - public static UserAudienceArgument of( - final String name, - final boolean allowOffline, - final PlayerType limitTo) { + public static ProfileAudienceArgument of( + String name, + boolean allowOffline, + PlayerType limitTo) { return of(name, false, allowOffline, limitTo); } - public static UserAudienceArgument ofOnline(final String name, final PlayerType limitTo) { + public static ProfileAudienceArgument ofOnline(String name, PlayerType limitTo) { return of(name, false, false, limitTo); } - public static UserAudienceArgument ofOnline(final String name, final boolean allowUuid) { + public static ProfileAudienceArgument ofOnline(String name, boolean allowUuid) { return of(name, allowUuid, false, PlayerType.ALL_PLAYERS); } - public static CommandArgument ofOnline(final String name) { + public static CommandArgument ofOnline(String name) { return of(name, false, false, PlayerType.ALL_PLAYERS); } - public static UserAudienceArgument of(final String name, final boolean allowOffline) { + public static ProfileAudienceArgument of(String name, boolean allowOffline) { return of(name, false, allowOffline, PlayerType.ALL_PLAYERS); } - public enum PlayerType { - ALL_PLAYERS, - ONLY_BEDROCK, - ONLY_JAVA - } - @RequiredArgsConstructor - public static final class UserAudienceParser - implements ArgumentParser { + public static final class ProfileAudienceParser + implements ArgumentParser { private final boolean allowUuid; private final boolean allowOffline; private final PlayerType limitTo; @Override - public @NonNull ArgumentParseResult parse( - final @NonNull CommandContext<@NonNull UserAudience> commandContext, - final @NonNull Queue<@NonNull String> inputQueue) { + public @NonNull ArgumentParseResult parse( + @NonNull CommandContext<@NonNull UserAudience> commandContext, + @NonNull Queue<@NonNull String> inputQueue) { CommandUtil commandUtil = commandContext.get("CommandUtil"); String input = inputQueue.poll(); @@ -111,7 +107,7 @@ public final class UserAudienceArgument extends CommandArgument 16) { // This must be a UUID. @@ -146,46 +142,39 @@ public final class UserAudienceArgument extends CommandArgument suggestions( - final @NonNull CommandContext commandContext, - final @NonNull String input) { - final CommandUtil commandUtil = commandContext.get("CommandUtil"); - final String trimmedInput = input.trim(); + @NonNull CommandContext commandContext, + @NonNull String input) { + CommandUtil commandUtil = commandContext.get("CommandUtil"); + String trimmedInput = input.trim(); if (trimmedInput.isEmpty()) { return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo)); } - final String lowercaseInput = input.toLowerCase(Locale.ROOT); - final ImmutableList.Builder builder = ImmutableList.builder(); + String lowercaseInput = input.toLowerCase(Locale.ROOT); + ImmutableList.Builder builder = ImmutableList.builder(); for (final String player : commandUtil.getOnlineUsernames(limitTo)) { if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) { @@ -205,7 +194,7 @@ public final class UserAudienceArgument extends CommandArgument { /* NOP */ }, + null, + () -> true, // remove this if/when we add some form of reload support + logger::error, + logger::info, + Constants.DEBUG_MODE, + Constants.DEBUG_MODE, + Constants.DEBUG_MODE + ); + + metricsBase.addCustomChart( + new SingleLineChart("players", api::getPlayerCount) + ); + + metricsBase.addCustomChart( + new DrilldownPie("player_count", () -> { + int playerCount = api.getPlayerCount(); + // 0 = 0 - 4, 9 = 5 - 9, etc. + int category = playerCount / 5 * 5; + String categoryName = category + " - " + (category + 4); + + return Collections.singletonMap( + implementationName, + Collections.singletonMap(categoryName, 1) + ); + }) + ); + + metricsBase.addCustomChart( + new SimplePie("authentication", + () -> platformUtils.authType().name().toLowerCase(Locale.ROOT)) + ); + + metricsBase.addCustomChart( + new SimplePie("floodgate_version", () -> Constants.VERSION) + ); + + metricsBase.addCustomChart( + new DrilldownPie("platform", () -> Collections.singletonMap( + implementationName, + Collections.singletonMap(platformUtils.serverImplementationName(), 1) + ))); + + metricsBase.addCustomChart( + new DrilldownPie("minecraft_version", () -> { + // e.g.: 1.16.5 => (Spigot, 1) + return Collections.singletonMap( + implementationName, + Collections.singletonMap(platformUtils.minecraftVersion(), 1) + ); + }) + ); + + // Source: Geyser + metricsBase.addCustomChart(new DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); + String javaVersion = System.getProperty("java.version"); + Map entry = new HashMap<>(); + entry.put(javaVersion, 1); + + String majorVersion = javaVersion.split("\\.")[0]; + String release; + + int indexOf = javaVersion.lastIndexOf('.'); + + if (majorVersion.equals("1")) { + release = "Java " + javaVersion.substring(0, indexOf); + } else { + Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion); + if (versionMatcher.find()) { + majorVersion = versionMatcher.group(0); + } + release = "Java " + majorVersion; + } + map.put(release, entry); + return map; + })); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java index 28c5abaf..63605c66 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java +++ b/core/src/main/java/org/geysermc/floodgate/util/ReflectionUtils.java @@ -269,6 +269,11 @@ public final class ReflectionUtils { return (T) getValue(instance, getField(instance.getClass(), fieldName)); } + @Nullable + public static T castedStaticValue(Field field) { + return getCastedValue(null, field); + } + /** * Set the value of a field. This method make the field accessible and then sets the value.
* This method doesn't throw an exception when failed, but it'll log the error to the console. diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index a1066356..27d23b77 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -58,5 +58,9 @@ player-link: # you have limited internet access. enable-global-linking: true +metrics: + enabled: true + uuid: ${metrics.uuid} + # Do not change this -config-version: 2 +config-version: 3 diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index d08abbfa..38cb4a52 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit d08abbfab6e15bee15ba356ba4aa2a7ac0e329ce +Subproject commit 38cb4a52df713cb0bc1738370aa8135c01f0cabc diff --git a/database/mongo/build.gradle.kts b/database/mongo/build.gradle.kts new file mode 100644 index 00000000..e20d1f12 --- /dev/null +++ b/database/mongo/build.gradle.kts @@ -0,0 +1,11 @@ +val mongoClientVersion = "4.4.1" + +dependencies { + provided(projects.core) + implementation("org.mongodb", "mongodb-driver-sync" , mongoClientVersion) +} + +description = "The Floodgate database extension for MongoDB" + +relocate("com.mongodb") +relocate("org.bson") \ No newline at end of file diff --git a/database/mongo/src/main/java/org/geysermc/floodgate/database/MongoDbDatabase.java b/database/mongo/src/main/java/org/geysermc/floodgate/database/MongoDbDatabase.java new file mode 100644 index 00000000..19f9058b --- /dev/null +++ b/database/mongo/src/main/java/org/geysermc/floodgate/database/MongoDbDatabase.java @@ -0,0 +1,343 @@ +/* + * 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.database; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCredential; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.client.model.Indexes; +import com.mongodb.client.model.UpdateOptions; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.Instant; +import java.util.ArrayList; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.internal.Base64; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.link.LinkRequest; +import org.geysermc.floodgate.api.link.LinkRequestResult; +import org.geysermc.floodgate.database.config.MongoConfig; +import org.geysermc.floodgate.link.CommonPlayerLink; +import org.geysermc.floodgate.link.LinkRequestImpl; +import org.geysermc.floodgate.util.LinkedPlayer; + +public class MongoDbDatabase extends CommonPlayerLink { + private MongoClient client; + private MongoDatabase database; + private MongoCollection linkedPlayer; + private MongoCollection linkedPlayerRequests; + + @Override + public void load() { + getLogger().info("Connecting to MongoDB database..."); + try { + MongoConfig databaseConfig = getConfig(MongoConfig.class); + + MongoClientSettings.Builder settings = MongoClientSettings.builder(); + settings.applyToConnectionPoolSettings(builder -> { + builder.maxSize(10); + builder.minSize(2); + }); + + if (databaseConfig.getMongouri().isEmpty()) { + settings.credential( + MongoCredential.createCredential( + databaseConfig.getUsername(), + databaseConfig.getDatabase(), + databaseConfig.getPassword().toCharArray() + ) + ); + } else { + settings.applyConnectionString(new ConnectionString(databaseConfig.getMongouri())); + } + + client = MongoClients.create(settings.build()); + + database = client.getDatabase(databaseConfig.getDatabase()); + + linkedPlayer = database.getCollection("LinkedPlayers"); + if (collectionNotExists("LinkedPlayers")) { + database.createCollection("LinkedPlayers"); + + linkedPlayer.createIndex(new Document("bedrockId", 1), + new IndexOptions().unique(true)); // primary key equivalent + linkedPlayer.createIndex(Indexes.ascending("javaUniqueId")); + } + + linkedPlayerRequests = database.getCollection("LinkedPlayerRequests"); + if (collectionNotExists("LinkedPlayerRequests")) { + database.createCollection("LinkedPlayerRequests"); + + linkedPlayerRequests.createIndex(new Document("bedrockId", 1), + new IndexOptions().unique(true)); // primary key equivalent + linkedPlayerRequests.createIndex(Indexes.ascending("requestTime")); + } + + getLogger().info("Connected to MongoDB database."); + } catch (Exception exception) { + getLogger().error("Error while loading database", exception); + } + } + + @Override + public void stop() { + super.stop(); + client.close(); + } + + @Override + @NonNull + public CompletableFuture getLinkedPlayer(@NonNull UUID bedrockId) { + return CompletableFuture.supplyAsync(() -> { + try { + Bson filter = Filters.eq("bedrockId", uuidToBytes(bedrockId)); + + try (MongoCursor cursor = linkedPlayer.find(filter).cursor()) { + if (cursor.hasNext()) { + Document document = cursor.next(); + String javaUsername = document.getString("javaUsername"); + UUID javaUniqueId = bytesToUUID(document.getString("javaUniqueId")); + + return LinkedPlayer.of(javaUsername, javaUniqueId, bedrockId); + } + } + + return null; + } catch (Exception exception) { + getLogger().error("Error while getting LinkedPlayer", exception); + throw new CompletionException("Error while getting LinkedPlayer", exception); + } + }, getExecutorService()); + } + + @Override + @NonNull + public CompletableFuture isLinkedPlayer(@NonNull UUID playerId) { + return CompletableFuture.supplyAsync(() -> { + try { + String uuidBytes = uuidToBytes(playerId); + Bson filter = Filters.or( + Filters.eq("bedrockId", uuidBytes), + Filters.eq("javaUniqueId", uuidBytes) + ); + try (MongoCursor cursor = linkedPlayer.find(filter).cursor()) { + return cursor.hasNext(); + } + } catch (Exception exception) { + getLogger().error("Error while checking if player is a LinkedPlayer", exception); + throw new CompletionException( + "Error while checking if player is a LinkedPlayer", exception + ); + } + }, getExecutorService()); + } + + @Override + @NonNull + public CompletableFuture linkPlayer( + @NonNull UUID bedrockId, + @NonNull UUID javaId, + @NonNull String javaUsername) { + return CompletableFuture.runAsync( + () -> linkPlayer0(bedrockId, javaId, javaUsername), + getExecutorService()); + } + + private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) { + try { + Bson filter = Filters.eq("javaUsername", javaUsername); + Document create = new Document("bedrockId", uuidToBytes(bedrockId)) + .append("javaUniqueId", uuidToBytes(javaId)) + .append("javaUsername", javaUsername); + Document update = new Document("$set", create); + + linkedPlayer.updateOne(filter, update, new UpdateOptions().upsert(true)); + // The upsert option will create a new document if the filter doesn't match anything. + // Or will update the document if it does match. + } catch (Exception exception) { + getLogger().error("Error while linking player", exception); + throw new CompletionException("Error while linking player", exception); + } + } + + @Override + @NonNull + public CompletableFuture unlinkPlayer(@NonNull UUID javaId) { + return CompletableFuture.runAsync(() -> { + try { + String uuidBytes = uuidToBytes(javaId); + + Bson filter = Filters.and( + Filters.eq("javaUniqueId", uuidBytes), + Filters.eq("bedrockId", uuidBytes) + ); + + linkedPlayer.deleteMany(filter); + } catch (Exception exception) { + getLogger().error("Error while unlinking player", exception); + throw new CompletionException("Error while unlinking player", exception); + } + }, getExecutorService()); + } + + @Override + @NonNull + public CompletableFuture createLinkRequest( + @NonNull UUID javaId, + @NonNull String javaUsername, + @NonNull String bedrockUsername) { + return CompletableFuture.supplyAsync(() -> { + String linkCode = createCode(); + + createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername); + + return linkCode; + }, getExecutorService()); + } + + private void createLinkRequest0( + String javaUsername, + UUID javaId, + String linkCode, + String bedrockUsername) { + try { + Bson filter = Filters.eq("javaUsername", javaUsername); + Document create = new Document("javaUsername", javaUsername) + .append("javaUniqueId", uuidToBytes(javaId)) + .append("linkCode", linkCode) + .append("bedrockUsername", bedrockUsername) + .append("requestTime", Instant.now().getEpochSecond()); + Document update = new Document("$set", create); + + linkedPlayerRequests.updateOne(filter, update, new UpdateOptions().upsert(true)); + // The upsert option will create a new document if the filter doesn't match anything. + // Or will update the document if it does match. + } catch (Exception exception) { + getLogger().error("Error while linking player", exception); + throw new CompletionException("Error while linking player", exception); + } + } + + private void removeLinkRequest(String javaUsername) { + try { + Document filter = new Document("javaUsername", javaUsername); + linkedPlayerRequests.deleteMany(filter); + } catch (Exception exception) { + getLogger().error("Error while cleaning up LinkRequest", exception); + } + } + + @Override + @NonNull + public CompletableFuture verifyLinkRequest( + @NonNull UUID bedrockId, + @NonNull String javaUsername, + @NonNull String bedrockUsername, + @NonNull String code) { + return CompletableFuture.supplyAsync(() -> { + LinkRequest request = getLinkRequest0(javaUsername); + + if (request == null || !isRequestedPlayer(request, bedrockId)) { + return LinkRequestResult.NO_LINK_REQUESTED; + } + + if (!request.getLinkCode().equals(code)) { + return LinkRequestResult.INVALID_CODE; + } + + // link request can be removed. Doesn't matter if the request is expired or not + removeLinkRequest(javaUsername); + + if (request.isExpired(getVerifyLinkTimeout())) { + return LinkRequestResult.REQUEST_EXPIRED; + } + + linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername); + return LinkRequestResult.LINK_COMPLETED; + }, getExecutorService()); + } + + private LinkRequest getLinkRequest0(String javaUsername) { + try { + Bson filter = Filters.eq("javaUsername", javaUsername); + try (MongoCursor cursor = linkedPlayerRequests.find(filter).cursor()) { + if (cursor.hasNext()) { + Document document = cursor.next(); + UUID javaId = bytesToUUID(document.getString("javaUniqueId")); + String linkCode = document.getString("linkCode"); + String bedrockUsername = document.getString("bedrockUsername"); + long requestTime = document.getLong("requestTime"); + return new LinkRequestImpl(javaUsername, javaId, linkCode, bedrockUsername, + requestTime); + } + } + } catch (Exception exception) { + getLogger().error("Error while getLinkRequest", exception); + throw new CompletionException("Error while getLinkRequest", exception); + } + return null; + } + + public void cleanLinkRequests() { + try { + Document filter = new Document("requestTime", + new Document("$lt", Instant.now().getEpochSecond() - getVerifyLinkTimeout())); + linkedPlayerRequests.deleteMany(filter); + } catch (Exception exception) { + getLogger().error("Error while cleaning up link requests", exception); + } + } + + private String uuidToBytes(UUID uuid) { + byte[] uuidBytes = new byte[16]; + ByteBuffer.wrap(uuidBytes) + .order(ByteOrder.BIG_ENDIAN) + .putLong(uuid.getMostSignificantBits()) + .putLong(uuid.getLeastSignificantBits()); + return Base64.encode(uuidBytes); + } + + private UUID bytesToUUID(String uuidBytes) { + ByteBuffer buf = ByteBuffer.wrap(Base64.decode(uuidBytes)); + return new UUID(buf.getLong(), buf.getLong()); + } + + public boolean collectionNotExists(final String collectionName) { + return !database.listCollectionNames().into(new ArrayList<>()).contains(collectionName); + } + +} diff --git a/database/mongo/src/main/java/org/geysermc/floodgate/database/config/MongoConfig.java b/database/mongo/src/main/java/org/geysermc/floodgate/database/config/MongoConfig.java new file mode 100644 index 00000000..e7cfae8e --- /dev/null +++ b/database/mongo/src/main/java/org/geysermc/floodgate/database/config/MongoConfig.java @@ -0,0 +1,37 @@ +/* + * 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.database.config; + +import lombok.Getter; + +@Getter +public class MongoConfig implements DatabaseConfig { + private String hostname = "localhost"; + private String database = "floodgate"; + private String username = "floodgate"; + private String password; + private String mongouri = ""; +} diff --git a/database/mongo/src/main/resources/init.json b/database/mongo/src/main/resources/init.json new file mode 100644 index 00000000..f161937f --- /dev/null +++ b/database/mongo/src/main/resources/init.json @@ -0,0 +1,4 @@ +{ + "mainClass": "org.geysermc.floodgate.database.MongoDbDatabase", + "config": "mongo.yml" +} \ No newline at end of file diff --git a/database/mongo/src/main/resources/mongo.yml b/database/mongo/src/main/resources/mongo.yml new file mode 100644 index 00000000..719af986 --- /dev/null +++ b/database/mongo/src/main/resources/mongo.yml @@ -0,0 +1,5 @@ +hostname: "localhost" +database: "floodgate" +username: "floodgate" +password: "" +mongouri: "" \ No newline at end of file diff --git a/ruleset.xml b/ruleset.xml index ca7c5bae..a078f6db 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -10,10 +10,6 @@ .*/CommonPlayerLink.* - - .*/DefaultConfigHandler.* - - .*/ConfigFileUpdater.* .*/FloodgateConfig.* diff --git a/settings.gradle.kts b/settings.gradle.kts index 7abe3a7f..7df9d09c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,8 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { +// mavenLocal() + // Geyser, Cumulus etc. maven("https://repo.opencollab.dev/maven-releases") { mavenContent { releasesOnly() } @@ -42,8 +44,6 @@ pluginManagement { } plugins { id("net.kyori.blossom") version "1.2.0" - id("net.kyori.indra") - id("net.kyori.indra.git") } includeBuild("build-logic") } @@ -57,5 +57,7 @@ include(":spigot") include(":velocity") include(":sqlite") include(":mysql") +include(":mongo") project(":sqlite").projectDir = file("database/sqlite") project(":mysql").projectDir = file("database/mysql") +project(":mongo").projectDir = file("database/mongo") diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts index f0d0fe9a..8bd8b9c0 100644 --- a/spigot/build.gradle.kts +++ b/spigot/build.gradle.kts @@ -5,12 +5,9 @@ var gsonVersion = "2.8.5" dependencies { api(projects.core) + implementation("cloud.commandframework", "cloud-bukkit", Versions.cloudVersion) // hack to make pre 1.12 work implementation("com.google.guava", "guava", guavaVersion) - - implementation("cloud.commandframework", "cloud-bukkit", Versions.cloudVersion) - implementation("net.kyori", "adventure-text-serializer-legacy", Versions.adventureApiVersion) - implementation("net.kyori", "adventure-text-serializer-gson", Versions.adventureApiVersion) } relocate("com.google.inject") @@ -24,7 +21,7 @@ relocate("com.google.guava") relocate("it.unimi") // these dependencies are already present on the platform -provided("org.spigotmc", "spigot-api", Versions.spigotVersion) +provided("com.destroystokyo.paper", "paper-api", Versions.spigotVersion) provided("com.mojang", "authlib", authlibVersion) provided("io.netty", "netty-transport", Versions.nettyVersion) provided("io.netty", "netty-codec", Versions.nettyVersion) diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java index 8fe74af9..6254cc17 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java +++ b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java @@ -28,13 +28,17 @@ package org.geysermc.floodgate; import com.google.inject.Guice; import com.google.inject.Injector; import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.module.PaperListenerModule; import org.geysermc.floodgate.module.PluginMessageModule; import org.geysermc.floodgate.module.ServerCommonModule; import org.geysermc.floodgate.module.SpigotAddonModule; import org.geysermc.floodgate.module.SpigotCommandModule; import org.geysermc.floodgate.module.SpigotListenerModule; import org.geysermc.floodgate.module.SpigotPlatformModule; +import org.geysermc.floodgate.util.ReflectionUtils; +import org.geysermc.floodgate.util.SpigotHandshakeHandler; import org.geysermc.floodgate.util.SpigotProtocolSupportHandler; import org.geysermc.floodgate.util.SpigotProtocolSupportListener; @@ -59,13 +63,20 @@ public final class SpigotPlugin extends JavaPlugin { @Override public void onEnable() { + boolean usePaperListener = ReflectionUtils.getClassSilently( + "com.destroystokyo.paper.event.profile.PreFillProfileEvent") != null; + platform.enable( new SpigotCommandModule(this), - new SpigotListenerModule(), new SpigotAddonModule(), - new PluginMessageModule() + new PluginMessageModule(), + (usePaperListener ? new PaperListenerModule() : new SpigotListenerModule()) ); + //todo add proper support for disabling things on shutdown and enabling this on enable + injector.getInstance(HandshakeHandlers.class) + .addHandshakeHandler(injector.getInstance(SpigotHandshakeHandler.class)); + // add ProtocolSupport support (hack) if (getServer().getPluginManager().getPlugin("ProtocolSupport") != null) { injector.getInstance(SpigotProtocolSupportHandler.class); diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java index 25eb6d24..bc5742db 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java @@ -42,6 +42,7 @@ import org.geysermc.floodgate.util.ProxyUtils; public final class SpigotDataHandler extends CommonDataHandler { private Object networkManager; private FloodgatePlayer player; + private boolean proxyData; public SpigotDataHandler( FloodgateHandshakeHandler handshakeHandler, @@ -52,7 +53,6 @@ public final class SpigotDataHandler extends CommonDataHandler { @Override protected void setNewIp(Channel channel, InetSocketAddress newIp) { - //todo the socket address will be overridden when bungeeData is true setValue(networkManager, ClassNames.SOCKET_ADDRESS, newIp); } @@ -76,20 +76,22 @@ public final class SpigotDataHandler extends CommonDataHandler { // the server will do all the work if BungeeCord mode is enabled, // otherwise we have to help the server. - boolean bungeeData = ProxyUtils.isProxyData(); + proxyData = ProxyUtils.isProxyData(); - if (!bungeeData) { + if (!proxyData) { // Use a spoofedUUID for initUUID (just like Bungeecord) setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId()); } - return bungeeData; + // we can only remove the handler if the data is proxy data and username validation doesn't + // exist. Otherwise, we need to wait and disable it. + return proxyData && ClassNames.PAPER_DISABLE_USERNAME_VALIDATION == null; } @Override protected boolean shouldCallFireRead(Object queuedPacket) { - // we have to ignore the 'login start' packet if BungeeCord mode is disabled, - // otherwise the server might ask the user to login + // we have to ignore the 'login start' packet, + // otherwise the server will ask the user to login try { if (checkAndHandleLogin(queuedPacket)) { return false; @@ -136,6 +138,16 @@ public final class SpigotDataHandler extends CommonDataHandler { return true; } + if (ClassNames.PAPER_DISABLE_USERNAME_VALIDATION != null) { + // ensure that Paper will not be checking + setValue(packetListener, ClassNames.PAPER_DISABLE_USERNAME_VALIDATION, true); + if (proxyData) { + // the server will handle the rest if we have proxy data + ctx.pipeline().remove(this); + return false; + } + } + // set the player his GameProfile, we can't change the username without this GameProfile gameProfile = new GameProfile( player.getCorrectUniqueId(), player.getCorrectUsername() diff --git a/spigot/src/main/java/org/geysermc/floodgate/listener/PaperProfileListener.java b/spigot/src/main/java/org/geysermc/floodgate/listener/PaperProfileListener.java new file mode 100644 index 00000000..5672ead8 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/listener/PaperProfileListener.java @@ -0,0 +1,65 @@ +/* + * 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.listener; + +import com.destroystokyo.paper.event.profile.PreFillProfileEvent; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.google.inject.Inject; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +public final class PaperProfileListener implements Listener { + @Inject private SimpleFloodgateApi api; + + @EventHandler + public void onFill(PreFillProfileEvent event) { + UUID id = event.getPlayerProfile().getId(); + if (id == null) { + return; + } + + FloodgatePlayer player = api.getPlayer(id); + if (player == null || player.isLinked()) { + return; + } + + // back when this event got added the PlayerProfile class didn't have the + // hasProperty / hasTextures methods + if (event.getPlayerProfile().getProperties().stream().anyMatch( + prop -> "textures".equals(prop.getName()))) { + return; + } + + Set properties = new HashSet<>(event.getPlayerProfile().getProperties()); + properties.add(new ProfileProperty("textures", "", "")); + event.setProperties(properties); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java index d1337163..3574af44 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java +++ b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java @@ -35,9 +35,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.util.LanguageManager; -import org.geysermc.floodgate.util.SpigotCommandUtil; public final class SpigotListener implements Listener { @Inject private SimpleFloodgateApi api; @@ -68,7 +66,5 @@ public final class SpigotListener implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onPlayerQuit(PlayerQuitEvent event) { api.playerRemoved(event.getPlayer().getUniqueId()); - - SpigotCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/PaperListenerModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/PaperListenerModule.java new file mode 100644 index 00000000..fb7249fb --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/module/PaperListenerModule.java @@ -0,0 +1,46 @@ +/* + * 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.module; + +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.bukkit.event.Listener; +import org.geysermc.floodgate.listener.PaperProfileListener; +import org.geysermc.floodgate.register.ListenerRegister; + +public class PaperListenerModule extends SpigotListenerModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public Listener paperProfileListener() { + return new PaperProfileListener(); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotCommandModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotCommandModule.java index 6d1b7f1a..4db0c9f6 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotCommandModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotCommandModule.java @@ -32,8 +32,12 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; import org.geysermc.floodgate.SpigotPlugin; +import org.geysermc.floodgate.command.util.Permission; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; @@ -42,6 +46,12 @@ import org.geysermc.floodgate.player.UserAudience; public final class SpigotCommandModule extends CommandModule { private final SpigotPlugin plugin; + @Override + protected void configure() { + super.configure(); + registerPermissions(); + } + @Provides @Singleton @SneakyThrows @@ -49,10 +59,26 @@ public final class SpigotCommandModule extends CommandModule { CommandManager commandManager = new BukkitCommandManager<>( plugin, CommandExecutionCoordinator.simpleCoordinator(), - commandUtil::getAudience, + commandUtil::getUserAudience, audience -> (CommandSender) audience.source() ); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); return commandManager; } + + private void registerPermissions() { + PluginManager manager = Bukkit.getPluginManager(); + for (Permission permission : Permission.values()) { + if (manager.getPermission(permission.get()) != null) { + continue; + } + + PermissionDefault defaultValue = + PermissionDefault.getByName(permission.defaultValue().name()); + + manager.addPermission(new org.bukkit.permissions.Permission( + permission.get(), defaultValue + )); + } + } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java index 41035211..ba9e64e6 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java @@ -33,7 +33,7 @@ import org.bukkit.event.Listener; import org.geysermc.floodgate.listener.SpigotListener; import org.geysermc.floodgate.register.ListenerRegister; -public final class SpigotListenerModule extends AbstractModule { +public class SpigotListenerModule extends AbstractModule { @Override protected void configure() { bind(new TypeLiteral>() {}).asEagerSingleton(); diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java index 6ffff89a..5547046b 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java @@ -42,6 +42,7 @@ import org.geysermc.floodgate.logger.JavaUtilFloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.platform.util.PlatformUtils; import org.geysermc.floodgate.pluginmessage.PluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageUtils; @@ -49,12 +50,18 @@ import org.geysermc.floodgate.pluginmessage.SpigotSkinApplier; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.SpigotCommandUtil; +import org.geysermc.floodgate.util.SpigotPlatformUtils; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; @RequiredArgsConstructor public final class SpigotPlatformModule extends AbstractModule { private final SpigotPlugin plugin; + @Override + protected void configure() { + bind(PlatformUtils.class).to(SpigotPlatformUtils.class); + } + @Provides @Singleton public JavaPlugin javaPlugin() { @@ -76,10 +83,9 @@ public final class SpigotPlatformModule extends AbstractModule { public CommandUtil commandUtil( FloodgateApi api, SpigotVersionSpecificMethods versionSpecificMethods, - FloodgateLogger logger, LanguageManager languageManager) { - return new SpigotCommandUtil(plugin.getServer(), api, versionSpecificMethods, plugin, - logger, languageManager); + return new SpigotCommandUtil( + languageManager, plugin.getServer(), api, versionSpecificMethods, plugin); } @Provides diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java index 3b3afd02..a73b5092 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java @@ -43,8 +43,6 @@ public class SpigotPluginMessageRegistration implements PluginMessageRegistratio (channel1, player, message) -> channel.handleServerCall(message, player.getUniqueId(), player.getName())); - //todo actually do something with the result, lol - messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier()); } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java index 4479ea66..66801723 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ClassNames.java @@ -25,6 +25,7 @@ package org.geysermc.floodgate.util; +import static org.geysermc.floodgate.util.ReflectionUtils.getField; import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType; import static org.geysermc.floodgate.util.ReflectionUtils.getMethod; @@ -37,6 +38,7 @@ import java.lang.reflect.Method; import java.net.SocketAddress; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("PMD.SystemPrintln") public class ClassNames { @@ -57,12 +59,17 @@ public class ClassNames { public static final Field LOGIN_PROFILE; public static final Field PACKET_LISTENER; + @Nullable public static final Field PAPER_DISABLE_USERNAME_VALIDATION; + @Nullable public static final Field PAPER_VELOCITY_SUPPORT; + public static final Method GET_PROFILE_METHOD; public static final Method LOGIN_DISCONNECT; public static final Method NETWORK_EXCEPTION_CAUGHT; public static final Method INIT_UUID; public static final Method FIRE_LOGIN_EVENTS; + public static final Field BUNGEE; + static { String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; SPIGOT_MAPPING_PREFIX = "net.minecraft.server." + version; @@ -71,7 +78,7 @@ public class ClassNames { // SpigotSkinApplier Class craftPlayerClass = ReflectionUtils.getClass( "org.bukkit.craftbukkit." + version + ".entity.CraftPlayer"); - GET_PROFILE_METHOD = ReflectionUtils.getMethod(craftPlayerClass, "getProfile"); + GET_PROFILE_METHOD = getMethod(craftPlayerClass, "getProfile"); checkNotNull(GET_PROFILE_METHOD, "Get profile method"); String nmsPackage = SPIGOT_MAPPING_PREFIX + '.'; @@ -157,6 +164,28 @@ public class ClassNames { FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); + + + PAPER_DISABLE_USERNAME_VALIDATION = getField(LOGIN_LISTENER, + "iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation"); + + if (Constants.DEBUG_MODE) { + System.out.println("Paper disable username validation field exists? " + + (PAPER_DISABLE_USERNAME_VALIDATION != null)); + } + + // ProxyUtils + Class spigotConfig = ReflectionUtils.getClass("org.spigotmc.SpigotConfig"); + checkNotNull(spigotConfig, "Spigot config"); + + BUNGEE = getField(spigotConfig, "bungee"); + checkNotNull(BUNGEE, "Bungee field"); + + Class paperConfig = ReflectionUtils.getClassSilently( + "com.destroystokyo.paper.PaperConfig"); + + PAPER_VELOCITY_SUPPORT = + paperConfig == null ? null : getField(paperConfig, "velocitySupport"); } private static Class getClassOrFallBack(String className, String fallbackName) { diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java b/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java index b788d518..9d982850 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/ProxyUtils.java @@ -25,45 +25,22 @@ package org.geysermc.floodgate.util; -import static com.google.common.base.Preconditions.checkNotNull; -import static org.geysermc.floodgate.util.ReflectionUtils.getField; - -import java.lang.reflect.Field; - @SuppressWarnings("ConstantConditions") public final class ProxyUtils { - private static final Field IS_BUNGEE_DATA; - private static final Field IS_MODERN_FORWARDING; - - static { - Class spigotConfig = ReflectionUtils.getClass("org.spigotmc.SpigotConfig"); - IS_BUNGEE_DATA = getField(spigotConfig, "bungee"); - checkNotNull(IS_BUNGEE_DATA, "bungee field cannot be null. Are you using CraftBukkit?"); - - Field velocitySupport; - try { - Class paperConfig = Class.forName("com.destroystokyo.paper.PaperConfig"); - velocitySupport = getField(paperConfig, "velocitySupport"); - } catch (ClassNotFoundException e) { - // We're not on a platform that has modern forwarding - velocitySupport = null; // NOPMD - there's really not a better way around this unless you want to use an optional - } - IS_MODERN_FORWARDING = velocitySupport; - } public static boolean isProxyData() { return isBungeeData() || isVelocitySupport(); } private static boolean isBungeeData() { - return ReflectionUtils.getCastedValue(null, IS_BUNGEE_DATA); + return ReflectionUtils.castedStaticValue(ClassNames.BUNGEE); } private static boolean isVelocitySupport() { - if (IS_MODERN_FORWARDING == null) { + if (ClassNames.PAPER_VELOCITY_SUPPORT == null) { return false; } - return ReflectionUtils.getCastedValue(null, IS_MODERN_FORWARDING); + return ReflectionUtils.castedStaticValue(ClassNames.PAPER_VELOCITY_SUPPORT); } -} +} \ No newline at end of file diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java index cc182d41..da3d65bc 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java @@ -25,44 +25,40 @@ package org.geysermc.floodgate.util; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; -import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; -import org.geysermc.floodgate.util.SpigotUserAudience.SpigotConsoleAudience; -import org.geysermc.floodgate.util.SpigotUserAudience.SpigotPlayerAudience; - -@RequiredArgsConstructor -public final class SpigotCommandUtil implements CommandUtil { - public static final @NonNull Map AUDIENCE_CACHE = new HashMap<>(); - private static UserAudience console; +import org.geysermc.floodgate.player.UserAudience.ConsoleAudience; +import org.geysermc.floodgate.player.UserAudience.PlayerAudience; +public final class SpigotCommandUtil extends CommandUtil { private final Server server; - private final FloodgateApi api; private final SpigotVersionSpecificMethods versionSpecificMethods; - private final JavaPlugin plugin; - private final FloodgateLogger logger; - private final LanguageManager manager; + private UserAudience console; + + public SpigotCommandUtil( + LanguageManager manager, + Server server, + FloodgateApi api, + SpigotVersionSpecificMethods versionSpecificMethods, + JavaPlugin plugin) { + super(manager, api); + this.server = server; + this.versionSpecificMethods = versionSpecificMethods; + this.plugin = plugin; + } @Override - public @NonNull UserAudience getAudience(final @NonNull Object sourceObj) { + public @NonNull UserAudience getUserAudience(final @NonNull Object sourceObj) { if (!(sourceObj instanceof CommandSender)) { throw new IllegalArgumentException("Source has to be a CommandSender!"); } @@ -72,89 +68,47 @@ public final class SpigotCommandUtil implements CommandUtil { if (console != null) { return console; } - return console = new SpigotConsoleAudience(source, this); + return console = new ConsoleAudience(source, this); } Player player = (Player) source; UUID uuid = player.getUniqueId(); + String username = player.getName(); String locale = versionSpecificMethods.getLocale(player); - return AUDIENCE_CACHE.computeIfAbsent(uuid, - $ -> new SpigotPlayerAudience(uuid, locale, source, true, this)); + return new PlayerAudience(uuid, username, locale, source,this, true); } @Override - public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { - Player player = server.getPlayer(username); - return player != null ? getAudience(player) : null; + protected String getUsernameFromSource(@NonNull Object source) { + return ((Player) source).getName(); } @Override - public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { - return new SpigotPlayerAudience(null, username, null, null, false, this); + protected UUID getUuidFromSource(@NonNull Object source) { + return ((Player) source).getUniqueId(); } @Override - public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { + protected Collection getOnlinePlayers() { + return server.getOnlinePlayers(); + } + + @Override + public Object getPlayerByUuid(@NonNull UUID uuid) { Player player = server.getPlayer(uuid); - return player != null ? getAudience(player) : null; + return player != null ? player : uuid; } @Override - public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { - return new SpigotPlayerAudience(uuid, null, null, null, false, this); - } - - @Override - public @NonNull Collection getOnlineUsernames(@NonNull PlayerType limitTo) { - Collection players = server.getOnlinePlayers(); - - Collection usernames = new ArrayList<>(); - switch (limitTo) { - case ALL_PLAYERS: - for (Player player : players) { - usernames.add(player.getName()); - } - break; - case ONLY_JAVA: - for (Player player : players) { - if (!api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getName()); - } - } - break; - case ONLY_BEDROCK: - for (Player player : players) { - if (api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getName()); - } - } - break; - default: - throw new IllegalStateException("Unknown PlayerType"); - } - return usernames; + public Object getPlayerByUsername(@NonNull String username) { + Player player = server.getPlayer(username); + return player != null ? player : username; } @Override public boolean hasPermission(Object player, String permission) { - return cast(player).hasPermission(permission); - } - - @Override - public Collection getOnlinePlayersWithPermission(String permission) { - List players = new ArrayList<>(); - for (Player player : Bukkit.getOnlinePlayers()) { - if (hasPermission(player, permission)) { - players.add(player); - } - } - return players; - } - - @Override - public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { - sendMessage(target, translateAndTransform(locale, message, args)); + return ((CommandSender) player).hasPermission(permission); } @Override @@ -163,10 +117,11 @@ public final class SpigotCommandUtil implements CommandUtil { } @Override - public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { - // Have to run this in the main thread so we don't get a `Asynchronous player kick!` error - Bukkit.getScheduler().runTask(plugin, - () -> cast(player).kickPlayer(translateAndTransform(locale, message, args))); + public void kickPlayer(Object player, String message) { + // can also be console + if (player instanceof Player) { + Bukkit.getScheduler().runTask(plugin, () -> ((Player) player).kickPlayer(message)); + } } @Override @@ -178,18 +133,4 @@ public final class SpigotCommandUtil implements CommandUtil { public boolean removePlayerFromWhitelist(UUID uuid, String username) { return WhitelistUtils.removePlayer(uuid, username); } - - public String translateAndTransform(String locale, TranslatableMessage message, Object... args) { - // unlike others, Bukkit doesn't have to transform a message into another class. - return message.translateMessage(manager, locale, args); - } - - private Player cast(Object instance) { - try { - return (Player) instance; - } catch (ClassCastException exception) { - logger.error("Failed to cast {} to Player", instance.getClass().getName()); - throw exception; - } - } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java new file mode 100644 index 00000000..b425fb67 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotHandshakeHandler.java @@ -0,0 +1,62 @@ +/* + * 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 java.util.UUID; +import org.geysermc.floodgate.api.handshake.HandshakeData; +import org.geysermc.floodgate.api.handshake.HandshakeHandler; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public class SpigotHandshakeHandler implements HandshakeHandler { + @Inject + private FloodgateLogger logger; + + @Override + public void handle(HandshakeData data) { + // we never have to do anything when BedrockData is null. + // BedrockData is null when something went wrong (e.g. invalid key / exception) + if (data.getBedrockData() == null) { + return; + } + + BedrockData bedrockData = data.getBedrockData(); + UUID correctUuid = data.getCorrectUniqueId(); + + // replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid + String[] split = data.getHostname().split("\0"); + if (split.length >= 3) { + if (logger.isDebug()) { + logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'", + split[1], bedrockData.getIp(), split[2], correctUuid.toString() + ); + } + split[1] = bedrockData.getIp(); + split[2] = correctUuid.toString(); + } + data.setHostname(String.join("\0", split)); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/player/ServerAudience.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotPlatformUtils.java similarity index 63% rename from core/src/main/java/org/geysermc/floodgate/player/ServerAudience.java rename to spigot/src/main/java/org/geysermc/floodgate/util/SpigotPlatformUtils.java index 7e4f5521..538c8ce6 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/ServerAudience.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotPlatformUtils.java @@ -23,23 +23,27 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.player; +package org.geysermc.floodgate.util; -import java.util.UUID; -import net.kyori.adventure.audience.Audience; -import org.checkerframework.checker.index.qual.NonNegative; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.geysermc.floodgate.player.UserAudience.ConsoleAudience; +import org.bukkit.Bukkit; +import org.geysermc.floodgate.platform.util.PlatformUtils; -public interface ServerAudience extends Audience { - @NonNull Iterable onlineAudiences(); +public class SpigotPlatformUtils extends PlatformUtils { + @Override + public AuthType authType() { + if (Bukkit.getOnlineMode()) { + return AuthType.ONLINE; + } + return ProxyUtils.isProxyData() ? AuthType.PROXIED : AuthType.OFFLINE; + } - @NonNull ConsoleAudience consoleAudience(); + @Override + public String minecraftVersion() { + return Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; + } - @NonNegative int onlineCount(); - - @Nullable UserAudience audienceOf(final @NonNull UUID uuid); - - @Nullable UserAudience audienceOf(final @NonNull String username); + @Override + public String serverImplementationName() { + return Bukkit.getServer().getName(); + } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java deleted file mode 100644 index de2ec182..00000000 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotUserAudience.java +++ /dev/null @@ -1,158 +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 java.util.UUID; -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.audience.ForwardingAudience; -import net.kyori.adventure.audience.MessageType; -import net.kyori.adventure.identity.Identity; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; -import org.geysermc.floodgate.player.UserAudience; - -@RequiredArgsConstructor -public class SpigotUserAudience implements UserAudience, ForwardingAudience.Single { - private final UUID uuid; - private final String locale; - private final CommandSender source; - private final CommandUtil commandUtil; - - @Override - public @NonNull UUID uuid() { - return uuid; - } - - @Override - public @NonNull String username() { - return source.getName(); - } - - @Override - public @NonNull String locale() { - return locale; - } - - @Override - public @NonNull CommandSender source() { - return source; - } - - @Override - public boolean hasPermission(@NonNull String permission) { - return source.hasPermission(permission); - } - - @Override - public void sendMessage( - @NonNull Identity source, - @NonNull Component message, - @NonNull MessageType type) { - this.source.sendMessage(GsonComponentSerializer.gson().serialize(message)); - } - - @Override - public void sendMessage(TranslatableMessage message, Object... args) { - commandUtil.sendMessage(source(), locale(), message, args); - } - - @Override - public void disconnect(@NonNull Component reason) { - if (source instanceof Player) { - ((Player) source).kickPlayer(GsonComponentSerializer.gson().serialize(reason)); - } - } - - @Override - public void disconnect(TranslatableMessage message, Object... args) { - commandUtil.kickPlayer(source(), locale(), message, args); - } - - @Override - public @NonNull Audience audience() { - return this; - } - - public static final class SpigotConsoleAudience extends SpigotUserAudience - implements ConsoleAudience { - public SpigotConsoleAudience(CommandSender source, CommandUtil commandUtil) { - super(new UUID(0, 0), "en_us", source, commandUtil); - } - - @Override - public void sendMessage( - @NonNull Identity source, - @NonNull Component message, - @NonNull MessageType type) { - source().sendMessage(LegacyComponentSerializer.legacySection().serialize(message)); - } - } - - public static final class SpigotPlayerAudience extends SpigotUserAudience - implements PlayerAudience { - - private final String username; - private final boolean online; - - public SpigotPlayerAudience( - UUID uuid, - String username, - String locale, - CommandSender source, - boolean online, - CommandUtil commandUtil) { - super(uuid, locale, source, commandUtil); - this.username = username; - this.online = online; - } - - public SpigotPlayerAudience( - UUID uuid, - String locale, - CommandSender source, - boolean online, - CommandUtil commandUtil) { - this(uuid, source.getName(), locale, source, online, commandUtil); - } - - @Override - public @NonNull String username() { - return username; - } - - @Override - public boolean online() { - return online; - } - } -} diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index 086e2ccc..2ddcfae5 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -5,7 +5,7 @@ var guavaVersion = "25.1-jre" dependencies { api(projects.core) - api("cloud.commandframework", "cloud-velocity", Versions.cloudVersion) + implementation("cloud.commandframework", "cloud-velocity", Versions.cloudVersion) } relocate("cloud.commandframework") @@ -14,7 +14,6 @@ relocate("io.leangen.geantyref") // these dependencies are already present on the platform -provided("net.kyori", "adventure-api", Versions.adventureApiVersion, 0b100) provided("com.google.code.gson", "gson", gsonVersion) provided("com.google.guava", "guava", guavaVersion) provided("com.google.inject", "guice", Versions.guiceVersion) diff --git a/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java index a37768bb..1a4d43b1 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java +++ b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java @@ -82,7 +82,7 @@ public final class VelocityInjector extends CommonPlatformInjector { @Override public boolean removeInjection() { - logger.error("Floodgate cannot remove itself from Bungee without a reboot"); + logger.error("Floodgate cannot remove itself from Velocity without a reboot"); return false; } diff --git a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java index 7ee887fa..8000f520 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java +++ b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java @@ -44,18 +44,19 @@ import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.util.GameProfile.Property; import io.netty.channel.Channel; import io.netty.util.AttributeKey; import java.lang.reflect.Field; -import java.util.ArrayList; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.TimeUnit; import net.kyori.adventure.text.Component; import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.util.LanguageManager; -import org.geysermc.floodgate.util.VelocityCommandUtil; public final class VelocityListener { private static final Field INITIAL_MINECRAFT_CONNECTION; @@ -89,6 +90,7 @@ public final class VelocityListener { .expireAfterAccess(20, TimeUnit.SECONDS) .build(); + @Inject private ProxyFloodgateConfig config; @Inject private ProxyFloodgateApi api; @Inject private LanguageManager languageManager; @Inject private FloodgateLogger logger; @@ -141,8 +143,17 @@ public final class VelocityListener { FloodgatePlayer player = playerCache.getIfPresent(event.getConnection()); if (player != null) { playerCache.invalidate(event.getConnection()); - event.setGameProfile(new GameProfile( - player.getCorrectUniqueId(), player.getCorrectUsername(), new ArrayList<>())); + + GameProfile profile = new GameProfile( + player.getCorrectUniqueId(), + player.getCorrectUsername(), + Collections.emptyList() + ); + // The texture properties addition is to fix the February 2 2022 Mojang authentication changes + if (!config.isSendFloodgateData() && !player.isLinked()) { + profile = profile.addProperty(new Property("textures", "", "")); + } + event.setGameProfile(profile); } } @@ -159,7 +170,5 @@ public final class VelocityListener { @Subscribe(order = PostOrder.LAST) public void onDisconnect(DisconnectEvent event) { api.playerRemoved(event.getPlayer().getUniqueId()); - - VelocityCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java index 58f694db..8678af5e 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java @@ -48,6 +48,7 @@ import org.geysermc.floodgate.logger.Slf4jFloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.platform.util.PlatformUtils; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.pluginmessage.PluginMessageManager; @@ -57,6 +58,7 @@ import org.geysermc.floodgate.pluginmessage.VelocityPluginMessageUtils; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.VelocityCommandUtil; +import org.geysermc.floodgate.util.VelocityPlatformUtils; import org.geysermc.floodgate.util.VelocitySkinApplier; import org.slf4j.Logger; @@ -66,25 +68,25 @@ public final class VelocityPlatformModule extends AbstractModule { @Override protected void configure() { - VelocityCommandUtil commandUtil = new VelocityCommandUtil(); - requestInjection(commandUtil); - bind(CommandUtil.class).to(VelocityCommandUtil.class); - bind(VelocityCommandUtil.class).toInstance(commandUtil); + bind(PlatformUtils.class).to(VelocityPlatformUtils.class); + } + @Provides + @Singleton + public CommandManager commandManager(CommandUtil commandUtil) { Injector child = guice.createChildInjector(new CloudInjectionModule<>( UserAudience.class, CommandExecutionCoordinator.simpleCoordinator(), - commandUtil::getAudience, + commandUtil::getUserAudience, audience -> (CommandSource) audience.source() )); CommandManager commandManager = child.getInstance(new Key>() {}); - bind(new Key>() {}).toInstance(commandManager); - commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + return commandManager; } @Provides @@ -99,8 +101,9 @@ public final class VelocityPlatformModule extends AbstractModule { @Provides @Singleton - public ListenerRegistration listenerRegistration(EventManager eventManager, - VelocityPlugin plugin) { + public ListenerRegistration listenerRegistration( + EventManager eventManager, + VelocityPlugin plugin) { return new VelocityListenerRegistration(eventManager, plugin); } diff --git a/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java b/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java deleted file mode 100644 index 46c55dc9..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/player/VelocityUserAudience.java +++ /dev/null @@ -1,134 +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.player; - -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.Player; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.audience.ForwardingAudience; -import net.kyori.adventure.audience.MessageType; -import net.kyori.adventure.identity.Identity; -import net.kyori.adventure.text.Component; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; - -@RequiredArgsConstructor -public abstract class VelocityUserAudience implements UserAudience, ForwardingAudience.Single { - private final UUID uuid; - private final String username; - private final String locale; - private final CommandSource source; - private final CommandUtil commandUtil; - - @Override - public @NonNull UUID uuid() { - return uuid; - } - - @Override - public @NonNull String username() { - return username; - } - - @Override - public @NonNull String locale() { - return locale; - } - - @Override - public @NonNull CommandSource source() { - return source; - } - - @Override - public boolean hasPermission(@NonNull String permission) { - return source.hasPermission(permission); - } - - @Override - public void sendMessage( - @NonNull Identity source, - @NonNull Component message, - @NonNull MessageType type) { - // apparently the console doesn't implement sendMessage with MessageType, - // so we'll just ignore it - this.source.sendMessage(source, message); - } - - @Override - public void sendMessage(TranslatableMessage message, Object... args) { - commandUtil.sendMessage(source(), locale(), message, args); - } - - @Override - public void disconnect(@NonNull Component reason) { - if (source instanceof Player) { - ((Player) source).disconnect(reason); - } - } - - @Override - public void disconnect(TranslatableMessage message, Object... args) { - commandUtil.kickPlayer(source(), locale(), message, args); - } - - @Override - public @NonNull Audience audience() { - return source; - } - - public static final class VelocityConsoleAudience extends VelocityUserAudience - implements ConsoleAudience { - - public VelocityConsoleAudience(CommandSource source, CommandUtil commandUtil) { - super(new UUID(0, 0), "CONSOLE", "en_us", source, commandUtil); - } - } - - public static final class VelocityPlayerAudience extends VelocityUserAudience - implements PlayerAudience { - private final boolean online; - - public VelocityPlayerAudience( - UUID uuid, - String username, - String locale, - CommandSource source, - boolean online, - CommandUtil commandUtil) { - super(uuid, username, locale, source, commandUtil); - this.online = online; - } - - @Override - public boolean online() { - return online; - } - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java index e399d607..a2c355f3 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java @@ -29,35 +29,29 @@ import com.google.inject.Inject; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Optional; import java.util.UUID; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.platform.command.CommandUtil; -import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.player.UserAudience; -import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; -import org.geysermc.floodgate.player.VelocityUserAudience.VelocityConsoleAudience; -import org.geysermc.floodgate.player.VelocityUserAudience.VelocityPlayerAudience; +import org.geysermc.floodgate.player.UserAudience.ConsoleAudience; +import org.geysermc.floodgate.player.UserAudience.PlayerAudience; -public final class VelocityCommandUtil implements CommandUtil { - public static final @NonNull Map AUDIENCE_CACHE = new HashMap<>(); +public final class VelocityCommandUtil extends CommandUtil { private static UserAudience console; @Inject private ProxyServer server; - @Inject private FloodgateApi api; - @Inject private FloodgateLogger logger; - @Inject private LanguageManager manager; + + @Inject + public VelocityCommandUtil(LanguageManager manager, FloodgateApi api) { + super(manager, api); + } @Override - public @NonNull UserAudience getAudience(@NonNull Object sourceObj) { + public @NonNull UserAudience getUserAudience(@NonNull Object sourceObj) { if (!(sourceObj instanceof CommandSource)) { throw new IllegalArgumentException("Can only work with CommandSource!"); } @@ -67,7 +61,7 @@ public final class VelocityCommandUtil implements CommandUtil { if (console != null) { return console; } - return console = new VelocityConsoleAudience(source, this); + return console = new ConsoleAudience(source, this); } Player player = (Player) source; @@ -75,84 +69,39 @@ public final class VelocityCommandUtil implements CommandUtil { String username = player.getUsername(); String locale = Utils.getLocale(player.getPlayerSettings().getLocale()); - return AUDIENCE_CACHE.computeIfAbsent(uuid, - $ -> new VelocityPlayerAudience(uuid, username, locale, source, true, this)); + return new PlayerAudience(uuid, username, locale, source, this, true); } @Override - public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { - return server.getPlayer(username) - .map(this::getAudience) - .orElse(null); + protected String getUsernameFromSource(@NonNull Object source) { + return ((Player) source).getUsername(); } @Override - public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { - return new VelocityPlayerAudience(null, username, null, null, false, this); + protected UUID getUuidFromSource(@NonNull Object source) { + return ((Player) source).getUniqueId(); } @Override - public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { - return server.getPlayer(uuid) - .map(this::getAudience) - .orElse(null); + protected Collection getOnlinePlayers() { + return server.getAllPlayers(); } @Override - public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { - return new VelocityPlayerAudience(uuid, null, null, null, false, this); + public Object getPlayerByUuid(@NonNull UUID uuid) { + Optional player = server.getPlayer(uuid); + return player.isPresent() ? player.get() : uuid; } @Override - public @NonNull Collection getOnlineUsernames(@NonNull PlayerType limitTo) { - Collection players = server.getAllPlayers(); - - Collection usernames = new ArrayList<>(); - switch (limitTo) { - case ALL_PLAYERS: - for (Player player : players) { - usernames.add(player.getUsername()); - } - break; - case ONLY_JAVA: - for (Player player : players) { - if (!api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getUsername()); - } - } - break; - case ONLY_BEDROCK: - for (Player player : players) { - if (api.isFloodgatePlayer(player.getUniqueId())) { - usernames.add(player.getUsername()); - } - } - break; - default: - throw new IllegalStateException("Unknown PlayerType"); - } - return usernames; + public Object getPlayerByUsername(@NonNull String username) { + Optional player = server.getPlayer(username); + return player.isPresent() ? player.get() : username; } @Override public boolean hasPermission(Object player, String permission) { - return cast(player).hasPermission(permission); - } - - @Override - public Collection getOnlinePlayersWithPermission(String permission) { - List players = new ArrayList<>(); - for (Player player : server.getAllPlayers()) { - if (hasPermission(player, permission)) { - players.add(player); - } - } - return players; - } - - @Override - public void sendMessage(Object target, String locale, TranslatableMessage message, Object... args) { - ((CommandSource) target).sendMessage(translateAndTransform(locale, message, args)); + return ((CommandSource) player).hasPermission(permission); } @Override @@ -161,23 +110,9 @@ public final class VelocityCommandUtil implements CommandUtil { } @Override - public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { - cast(player).disconnect(translateAndTransform(locale, message, args)); - } - - public Component translateAndTransform( - String locale, - TranslatableMessage message, - Object... args) { - return Component.text(message.translateMessage(manager, locale, args)); - } - - protected Player cast(Object instance) { - try { - return (Player) instance; - } catch (ClassCastException exception) { - logger.error("Failed to cast {} to Player", instance.getClass().getName()); - throw exception; + public void kickPlayer(Object player, String message) { + if (player instanceof Player) { + ((Player) player).disconnect(Component.text(message)); } } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityPlatformUtils.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityPlatformUtils.java new file mode 100644 index 00000000..0340cfac --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityPlatformUtils.java @@ -0,0 +1,51 @@ +/* + * 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.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.ProxyServer; +import org.geysermc.floodgate.platform.util.PlatformUtils; + +public final class VelocityPlatformUtils extends PlatformUtils { + @Inject + private ProxyServer server; + + @Override + public AuthType authType() { + return server.getConfiguration().isOnlineMode() ? AuthType.ONLINE : AuthType.OFFLINE; + } + + @Override + public String minecraftVersion() { + return ProtocolVersion.MAXIMUM_VERSION.getMostRecentSupportedVersion(); + } + + @Override + public String serverImplementationName() { + return server.getVersion().getName(); + } +}