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

Merge remote-tracking branch 'origin/dev/2.1.1' into feature/cumulus-1.1

# Conflicts:
#	build-logic/src/main/kotlin/Versions.kt
This commit is contained in:
Tim203
2022-06-06 10:47:48 +02:00
82 changed files with 2149 additions and 1813 deletions

View File

@@ -8,34 +8,35 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 1.8 - name: Set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'temurin'
java-version: '8'
cache: 'gradle'
- name: Build with Maven - name: Build with Maven
run: mvn -B package run: ./gradlew build
- name: Archive artifacts (Floodgate Bungee) - name: Archive artifacts (Floodgate Bungee)
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v2
if: success() if: success()
with: with:
name: Floodgate Bungee name: Floodgate Bungee
path: bungee/target/floodgate-bungee.jar path: bungee/build/libs/floodgate-bungee.jar
- name: Archive artifacts (Floodgate Spigot) - name: Archive artifacts (Floodgate Spigot)
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v2
if: success() if: success()
with: with:
name: Floodgate Spigot name: Floodgate Spigot
path: spigot/target/floodgate-spigot.jar path: spigot/build/libs/floodgate-spigot.jar
- name: Archive artifacts (Floodgate Velocity) - name: Archive artifacts (Floodgate Velocity)
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v2
if: success() if: success()
with: with:
name: Floodgate Velocity name: Floodgate Velocity
path: velocity/target/floodgate-velocity.jar path: velocity/build/libs/floodgate-velocity.jar

13
Jenkinsfile vendored
View File

@@ -11,11 +11,18 @@ pipeline {
stage ('Build') { stage ('Build') {
steps { steps {
sh 'git submodule update --init --recursive' sh 'git submodule update --init --recursive'
sh './gradlew clean build' rtGradleRun(
usesPlugin: true,
tool: 'Gradle 7',
buildFile: 'build.gradle.kts',
tasks: 'clean build',
)
} }
post { post {
success { 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: "", rootDir: "",
useWrapper: true, useWrapper: true,
buildFile: 'build.gradle.kts', buildFile: 'build.gradle.kts',
tasks: 'build artifactoryPublish', tasks: 'artifactoryPublish',
deployerId: "GRADLE_DEPLOYER", deployerId: "GRADLE_DEPLOYER",
resolverId: "GRADLE_RESOLVER" resolverId: "GRADLE_RESOLVER"
) )

View File

@@ -5,10 +5,10 @@
[![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/) [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](http://discord.geysermc.org/)
[![HitCount](https://hits.dwyl.com/GeyserMC/Floodgate.svg)](http://hits.dwyl.com/GeyserMC/Floodgate) [![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. 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). 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](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.
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.

View File

@@ -34,7 +34,7 @@ public interface InjectorAddon {
* used for third party things. * used for third party things.
* *
* @param channel the channel that the injector is injecting * @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); 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 * 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). * 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) { default void onChannelClosed(Channel channel) {
} }

View File

@@ -51,7 +51,7 @@ public class PropertyKey {
this.removable = removable; this.removable = removable;
} }
public Result isAddAllowed(Object obj) { //todo use for add and remove public Result isAddAllowed(Object obj) {
if (obj instanceof PropertyKey) { if (obj instanceof PropertyKey) {
PropertyKey propertyKey = (PropertyKey) obj; PropertyKey propertyKey = (PropertyKey) obj;

View File

@@ -26,6 +26,7 @@
object Versions { object Versions {
const val geyserVersion = "2.0.1-cumulus-SNAPSHOT" const val geyserVersion = "2.0.1-cumulus-SNAPSHOT"
const val cumulusVersion = "1.1-SNAPSHOT" const val cumulusVersion = "1.1-SNAPSHOT"
const val configUtilsVersion = "1.0-SNAPSHOT"
const valspigotVersion = "1.13-R0.1-SNAPSHOT" const valspigotVersion = "1.13-R0.1-SNAPSHOT"
const val fastutilVersion = "8.5.3" const val fastutilVersion = "8.5.3"
const val lombokVersion = "1.18.20" const val lombokVersion = "1.18.20"
@@ -33,8 +34,7 @@ object Versions {
const val nettyVersion = "4.1.49.Final" const val nettyVersion = "4.1.49.Final"
const val snakeyamlVersion = "1.28" const val snakeyamlVersion = "1.28"
const val cloudVersion = "1.5.0" const val cloudVersion = "1.5.0"
const val adventureApiVersion = "4.9.1" const val bstatsVersion = "3.0.0"
const val adventurePlatformVersion = "4.0.0"
const val javaWebsocketVersion = "1.5.2" const val javaWebsocketVersion = "1.5.2"

View File

@@ -23,30 +23,37 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import net.kyori.indra.git.IndraGitExtension import net.kyori.indra.git.IndraGitExtension
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.ProjectDependency
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.the 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? = fun Project.lastCommitHash(): String? =
the<IndraGitExtension>().commit()?.name?.substring(0, 7) the<IndraGitExtension>().commit()?.name?.substring(0, 7)
// retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project // retrieved from https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
// some properties might be specific to Jenkins // some properties might be specific to Jenkins
fun Project.branchName(): String = fun Project.branchName(): String =
System.getProperty("GIT_BRANCH", "local/dev") System.getenv("GIT_BRANCH") ?: "local/dev"
fun Project.buildNumber(): Int = fun Project.buildNumber(): Int =
Integer.parseInt(System.getProperty("BUILD_NUMBER", "-1")) Integer.parseInt(System.getenv("BUILD_NUMBER") ?: "-1")
fun Project.relocate(pattern: String) { fun Project.buildNumberAsString(): String =
tasks.named<ShadowJar>("shadowJar") { buildNumber().takeIf { it != -1 }?.toString() ?: "??"
relocate(pattern, "org.geysermc.floodgate.shaded.$pattern")
}
}
val providedDependencies = mutableMapOf<String, MutableSet<String>>() val providedDependencies = mutableMapOf<String, MutableSet<String>>()
val relocatedPackages = mutableMapOf<String, MutableSet<String>>()
fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) { fun Project.provided(pattern: String, name: String, version: String, excludedOn: Int = 0b110) {
providedDependencies.getOrPut(project.name) { mutableSetOf() } providedDependencies.getOrPut(project.name) { mutableSetOf() }
@@ -59,5 +66,10 @@ fun Project.provided(pattern: String, name: String, version: String, excludedOn:
fun Project.provided(dependency: ProjectDependency) = fun Project.provided(dependency: ProjectDependency) =
provided(dependency.group!!, dependency.name, dependency.version!!) 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 = private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String =
if (excludedOn and bit > 0) section else "" if (excludedOn and bit > 0) section else ""

View File

@@ -2,6 +2,7 @@ plugins {
`java-library` `java-library`
`maven-publish` `maven-publish`
// id("net.ltgt.errorprone") // id("net.ltgt.errorprone")
id("net.kyori.indra.git")
} }
dependencies { dependencies {
@@ -14,7 +15,7 @@ tasks {
expand( expand(
"id" to "floodgate", "id" to "floodgate",
"name" to "floodgate", "name" to "floodgate",
"version" to project.version, "version" to fullVersion(),
"description" to project.description, "description" to project.description,
"url" to "https://geysermc.org", "url" to "https://geysermc.org",
"author" to "GeyserMC" "author" to "GeyserMC"
@@ -32,11 +33,3 @@ java {
withSourcesJar() withSourcesJar()
} }
publishing {
publications.create<MavenPublication>("mavenJava") {
groupId = project.group as String
artifactId = "floodgate-" + project.name
version = project.version as String
}
}

View File

@@ -0,0 +1,34 @@
plugins {
id("floodgate.shadow-conventions")
id("com.jfrog.artifactory")
id("maven-publish")
}
publishing {
publications {
create<MavenPublication>("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)
}
}
}

View File

@@ -3,7 +3,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins { plugins {
id("floodgate.base-conventions") id("floodgate.base-conventions")
id("com.github.johnrengelman.shadow") id("com.github.johnrengelman.shadow")
id("com.jfrog.artifactory")
} }
tasks { tasks {
@@ -25,6 +24,12 @@ tasks {
exclude(dependency(string)) 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") { named("build") {
@@ -32,24 +37,18 @@ tasks {
} }
} }
publishing { fun addRelocations(project: Project, shadowJar: ShadowJar) {
publications.named<MavenPublication>("mavenJava") { callAddRelocations(project.configurations.api.get(), shadowJar)
artifact(tasks["shadowJar"]) callAddRelocations(project.configurations.implementation.get(), shadowJar)
artifact(tasks["sourcesJar"])
relocatedPackages[project.name]?.forEach { pattern ->
println("Relocating $pattern for ${shadowJar.project.name}")
shadowJar.relocate(pattern, "org.geysermc.floodgate.shadow.$pattern")
} }
} }
artifactory { fun callAddRelocations(configuration: Configuration, shadowJar: ShadowJar) =
publish { configuration.dependencies.forEach {
repository { if (it is ProjectDependency)
setRepoKey("maven-snapshots") addRelocations(it.dependencyProject, shadowJar)
setMavenCompatible(true)
}
defaults {
publishConfigs("archives")
setPublishArtifacts(true)
setPublishPom(true)
setPublishIvy(false)
}
}
} }

View File

@@ -3,6 +3,7 @@ plugins {
id("floodgate.build-logic") id("floodgate.build-logic")
// id("com.github.spotbugs") version "4.8.0" apply false // id("com.github.spotbugs") version "4.8.0" apply false
id("io.freefair.lombok") version "6.3.0" apply false id("io.freefair.lombok") version "6.3.0" apply false
// checkstyle
} }
allprojects { allprojects {
@@ -11,7 +12,10 @@ allprojects {
description = "Allows Bedrock players to join Java edition servers while keeping the server in online mode" description = "Allows Bedrock players to join Java edition servers while keeping the server in online mode"
} }
val platforms = setOf( val deployProjects = setOf(
projects.api,
// for future Floodgate integration + Fabric
projects.core,
projects.bungee, projects.bungee,
projects.spigot, projects.spigot,
projects.velocity projects.velocity
@@ -19,28 +23,32 @@ val platforms = setOf(
//todo re-add pmd and organisation/license/sdcm/issuemanagement stuff //todo re-add pmd and organisation/license/sdcm/issuemanagement stuff
val api: Project = projects.api.dependencyProject
subprojects { subprojects {
// apply(plugin = "pmd")
// apply(plugin = "com.github.spotbugs") // apply(plugin = "com.github.spotbugs")
apply { apply {
plugin("java-library") plugin("java-library")
// plugin("checkstyle")
plugin("io.freefair.lombok") plugin("io.freefair.lombok")
plugin("floodgate.build-logic") 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 val relativePath = projectDir.relativeTo(rootProject.projectDir).path
if (relativePath.startsWith("database" + File.separator)) { if (relativePath.startsWith("database" + File.separator)) {
group = rootProject.group as String + ".database" group = rootProject.group as String + ".database"
plugins.apply("floodgate.database-conventions") plugins.apply("floodgate.database-conventions")
} else { }
when (this) { when (this) {
in platforms -> plugins.apply("floodgate.shadow-conventions") in deployProjects -> plugins.apply("floodgate.publish-conventions")
api -> plugins.apply("floodgate.shadow-conventions")
else -> plugins.apply("floodgate.base-conventions") else -> plugins.apply("floodgate.base-conventions")
} }
} }
}

View File

@@ -4,10 +4,7 @@ var guavaVersion = "21.0"
dependencies { dependencies {
api(projects.core) api(projects.core)
implementation("cloud.commandframework", "cloud-bungee", Versions.cloudVersion) 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") relocate("com.google.inject")

View File

@@ -59,7 +59,7 @@ public final class BungeeInjector extends CommonPlatformInjector {
// (Instead of just replacing the ChannelInitializer which is only called for // (Instead of just replacing the ChannelInitializer which is only called for
// player <-> proxy) // player <-> proxy)
BungeeCustomPrepender customPrepender = new BungeeCustomPrepender( BungeeCustomPrepender customPrepender = new BungeeCustomPrepender(
this, ReflectionUtils.getCastedValue(null, framePrepender) this, ReflectionUtils.castedStaticValue(framePrepender)
); );
BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender); BungeeReflectionUtils.setFieldValue(null, framePrepender, customPrepender);

View File

@@ -36,6 +36,7 @@ import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; 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.event.PreLoginEvent;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler; 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.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.util.BungeeCommandUtil; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.ReflectionUtils; 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"); checkNotNull(PLAYER_NAME, "Initial name field cannot be null");
} }
@Inject private ProxyFloodgateConfig config;
@Inject private ProxyFloodgateApi api; @Inject private ProxyFloodgateApi api;
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@Inject private SkinApplier skinApplier;
@Inject @Inject
@Named("playerAttribute") @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) @EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDisconnect(PlayerDisconnectEvent event) { public void onPlayerDisconnect(PlayerDisconnectEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId()); api.playerRemoved(event.getPlayer().getUniqueId());
BungeeCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo
} }
} }

View File

@@ -46,6 +46,7 @@ import org.geysermc.floodgate.logger.JavaUtilFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; 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.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.pluginmessage.BungeePluginMessageRegistration; 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.pluginmessage.PluginMessageRegistration;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.util.BungeeCommandUtil; import org.geysermc.floodgate.util.BungeeCommandUtil;
import org.geysermc.floodgate.util.BungeePlatformUtils;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class BungeePlatformModule extends AbstractModule { public final class BungeePlatformModule extends AbstractModule {
private final BungeePlugin plugin; private final BungeePlugin plugin;
@Override
protected void configure() {
bind(PlatformUtils.class).to(BungeePlatformUtils.class);
}
@Provides @Provides
@Singleton @Singleton
public Plugin bungeePlugin() { public Plugin bungeePlugin() {
@@ -83,7 +90,7 @@ public final class BungeePlatformModule extends AbstractModule {
CommandManager<UserAudience> commandManager = new BungeeCommandManager<>( CommandManager<UserAudience> commandManager = new BungeeCommandManager<>(
plugin, plugin,
CommandExecutionCoordinator.simpleCoordinator(), CommandExecutionCoordinator.simpleCoordinator(),
commandUtil::getAudience, commandUtil::getUserAudience,
audience -> (CommandSender) audience.source() audience -> (CommandSender) audience.source()
); );
commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil));
@@ -92,11 +99,8 @@ public final class BungeePlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public CommandUtil commandUtil( public CommandUtil commandUtil(FloodgateApi api, LanguageManager languageManager) {
FloodgateApi api, return new BungeeCommandUtil(languageManager, plugin.getProxy(), api);
FloodgateLogger logger,
LanguageManager languageManager) {
return new BungeeCommandUtil(plugin.getProxy(), api, logger, languageManager);
} }
@Provides @Provides

View File

@@ -25,6 +25,10 @@
package org.geysermc.floodgate.pluginmessage; 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 lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -38,6 +42,12 @@ import org.geysermc.floodgate.skin.SkinData;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class BungeeSkinApplier implements SkinApplier { public final class BungeeSkinApplier implements SkinApplier {
private static final Field LOGIN_RESULT;
static {
LOGIN_RESULT = getFieldOfType(InitialHandler.class, LoginResult.class);
}
private final FloodgateLogger logger; private final FloodgateLogger logger;
@Override @Override
@@ -61,6 +71,7 @@ public final class BungeeSkinApplier implements SkinApplier {
if (loginResult == null) { if (loginResult == null) {
// id and name are unused and properties will be overridden // id and name are unused and properties will be overridden
loginResult = new LoginResult(null, null, null); loginResult = new LoginResult(null, null, null);
setValue(handler, LOGIN_RESULT, loginResult);
} }
Property property = new Property("textures", skinData.getValue(), skinData.getSignature()); Property property = new Property("textures", skinData.getValue(), skinData.getSignature());

View File

@@ -25,42 +25,29 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer; 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 net.md_5.bungee.api.connection.ProxiedPlayer;
import org.checkerframework.checker.nullness.qual.NonNull; 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.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; 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.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.player.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.util.BungeeUserAudience.BungeeConsoleAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.util.BungeeUserAudience.BungeePlayerAudience;
@RequiredArgsConstructor
public final class BungeeCommandUtil implements CommandUtil {
public static final @NonNull Map<UUID, UserAudience> AUDIENCE_CACHE = new HashMap<>();
private static UserAudience console;
public final class BungeeCommandUtil extends CommandUtil {
private final ProxyServer server; private final ProxyServer server;
private final FloodgateApi api; private UserAudience console;
private final FloodgateLogger logger; public BungeeCommandUtil(LanguageManager manager, ProxyServer server, FloodgateApi api) {
private final LanguageManager manager; super(manager, api);
this.server = server;
}
@Override @Override
public @NonNull UserAudience getAudience(@NonNull Object sourceObj) { public @NonNull UserAudience getUserAudience(@NonNull Object sourceObj) {
if (!(sourceObj instanceof CommandSender)) { if (!(sourceObj instanceof CommandSender)) {
throw new IllegalArgumentException("Can only work with CommandSource!"); throw new IllegalArgumentException("Can only work with CommandSource!");
} }
@@ -70,7 +57,7 @@ public final class BungeeCommandUtil implements CommandUtil {
if (console != null) { if (console != null) {
return console; return console;
} }
return console = new BungeeConsoleAudience(source, this); return console = new ConsoleAudience(source, this);
} }
ProxiedPlayer player = (ProxiedPlayer) source; ProxiedPlayer player = (ProxiedPlayer) source;
@@ -78,82 +65,39 @@ public final class BungeeCommandUtil implements CommandUtil {
String username = player.getName(); String username = player.getName();
String locale = Utils.getLocale(player.getLocale()); String locale = Utils.getLocale(player.getLocale());
return AUDIENCE_CACHE.computeIfAbsent(uuid, return new PlayerAudience(uuid, username, locale, source, this, true);
$ -> new BungeePlayerAudience(uuid, username, locale, source, true, this));
} }
@Override @Override
public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { protected String getUsernameFromSource(@NonNull Object source) {
ProxiedPlayer player = server.getPlayer(username); return ((ProxiedPlayer) source).getName();
return player != null ? getAudience(player) : null;
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { protected UUID getUuidFromSource(@NonNull Object source) {
return new BungeePlayerAudience(null, username, null, null, false, this); return ((ProxiedPlayer) source).getUniqueId();
} }
@Override @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); ProxiedPlayer player = server.getPlayer(uuid);
return player != null ? getAudience(player) : null; return player != null ? player : uuid;
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { public Object getPlayerByUsername(@NonNull String username) {
return new BungeePlayerAudience(uuid, null, null, null, false, this); ProxiedPlayer player = server.getPlayer(username);
} return player != null ? player : username;
@Override
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
Collection<ProxiedPlayer> players = server.getPlayers();
Collection<String> 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;
} }
@Override @Override
public boolean hasPermission(Object player, String permission) { public boolean hasPermission(Object player, String permission) {
return cast(player).hasPermission(permission); return ((CommandSender) player).hasPermission(permission);
}
@Override
public Collection<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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));
} }
@Override @Override
@@ -162,23 +106,10 @@ public final class BungeeCommandUtil implements CommandUtil {
} }
@Override @Override
public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { public void kickPlayer(Object player, String message) {
cast(player).disconnect(translateAndTransform(locale, message, args)); // can also be a console
} if (player instanceof ProxiedPlayer) {
((ProxiedPlayer) player).disconnect(message);
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;
} }
} }
} }

View File

@@ -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<String> 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();
}
}

View File

@@ -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;
}
}
}

364
checkstyle.xml Normal file
View File

@@ -0,0 +1,364 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.org (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
To suppress certain violations please review suppression filters.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
default="checkstyle-suppressions.xml" />
<property name="optional" value="true"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="100"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap">
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
</module>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces">
<property name="tokens"
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
</module>
<module name="LeftCurly">
<property name="tokens"
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
COMPACT_CTOR_DEF"/>
</module>
<module name="SuppressionXpathSingleFilter">
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
<property name="id" value="RightCurlyAlone"/>
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
or preceding-sibling::*[last()][self::LCURLY]]"/>
</module>
<module name="WhitespaceAfter">
<property name="tokens"
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<property name="ignoreEnhancedForColon" value="false"/>
<property name="tokens"
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="tokens"
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
COMPACT_CTOR_DEF"/>
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
ANNOTATION_DEF, RECORD_DEF"/>
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="PatternVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="RecordComponentName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Record component name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="RecordTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Record type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="2"/>
<property name="braceAdjustment" value="2"/>
<property name="caseIndent" value="2"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="2"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="0"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
RECORD_COMPONENT_DEF"/>
</module>
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
</module>
<module name="MethodParamPad">
<property name="tokens"
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/>
</module>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
LABELED_STAT, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad">
<property name="tokens"
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
RECORD_DEF"/>
</module>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,
TYPE_EXTENSION_AND "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
RECORD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments"
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph"/>
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="accessModifiers" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="MissingJavadocMethod">
<property name="scope" value="public"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,
COMPACT_CTOR_DEF"/>
</module>
<module name="MissingJavadocType">
<property name="scope" value="protected"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
RECORD_DEF, ANNOTATION_DEF"/>
<property name="excludeScope" value="nothing"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc"/>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation">
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
<module name="SuppressionXpathFilter">
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
default="checkstyle-xpath-suppressions.xml" />
<property name="optional" value="true"/>
</module>
</module>
</module>

View File

@@ -6,22 +6,26 @@ plugins {
dependencies { dependencies {
api(projects.api) api(projects.api)
api("org.geysermc.configutils", "configutils", Versions.configUtilsVersion)
api("com.google.inject", "guice", Versions.guiceVersion) api("com.google.inject", "guice", Versions.guiceVersion)
api("com.nukkitx.fastutil", "fastutil-short-object-maps", Versions.fastutilVersion) api("com.nukkitx.fastutil", "fastutil-short-object-maps", Versions.fastutilVersion)
api("com.nukkitx.fastutil", "fastutil-int-object-maps", Versions.fastutilVersion) api("com.nukkitx.fastutil", "fastutil-int-object-maps", Versions.fastutilVersion)
api("org.java-websocket", "Java-WebSocket", Versions.javaWebsocketVersion) api("org.java-websocket", "Java-WebSocket", Versions.javaWebsocketVersion)
api("net.kyori", "adventure-api", Versions.adventureApiVersion)
api("cloud.commandframework", "cloud-core", Versions.cloudVersion) api("cloud.commandframework", "cloud-core", Versions.cloudVersion)
api("org.yaml", "snakeyaml", Versions.snakeyamlVersion) api("org.yaml", "snakeyaml", Versions.snakeyamlVersion)
api("org.bstats", "bstats-base", Versions.bstatsVersion)
} }
// present on all platforms // present on all platforms
provided("io.netty", "netty-transport", Versions.nettyVersion) provided("io.netty", "netty-transport", Versions.nettyVersion)
provided("io.netty", "netty-codec", Versions.nettyVersion) provided("io.netty", "netty-codec", Versions.nettyVersion)
relocate("org.bstats")
configure<BlossomExtension> { configure<BlossomExtension> {
val constantsFile = "src/main/java/org/geysermc/floodgate/util/Constants.java" val constantsFile = "src/main/java/org/geysermc/floodgate/util/Constants.java"
replaceToken("\${floodgateVersion}", fullVersion(), constantsFile)
replaceToken("\${branch}", branchName(), constantsFile) replaceToken("\${branch}", branchName(), constantsFile)
replaceToken("\${buildNumber}", buildNumber(), constantsFile) replaceToken("\${buildNumber}", buildNumber(), constantsFile)
} }

View File

@@ -40,13 +40,14 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.module.ConfigLoadedModule; import org.geysermc.floodgate.module.ConfigLoadedModule;
import org.geysermc.floodgate.module.PostInitializeModule; import org.geysermc.floodgate.module.PostInitializeModule;
import org.geysermc.floodgate.news.NewsChecker; import org.geysermc.floodgate.news.NewsChecker;
import org.geysermc.floodgate.util.Metrics;
import org.geysermc.floodgate.util.PrefixCheckTask; import org.geysermc.floodgate.util.PrefixCheckTask;
public class FloodgatePlatform { public class FloodgatePlatform {
@@ -100,8 +101,6 @@ public class FloodgatePlatform {
InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY); InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY);
// todo provide build number and branch for Geyser dump
guice.getInstance(NewsChecker.class).start(); guice.getInstance(NewsChecker.class).start();
} }
@@ -125,6 +124,8 @@ public class FloodgatePlatform {
PrefixCheckTask.checkAndExecuteDelayed(config, logger); PrefixCheckTask.checkAndExecuteDelayed(config, logger);
guice.getInstance(Metrics.class);
return true; return true;
} }
@@ -139,6 +140,7 @@ public class FloodgatePlatform {
} }
} }
guice.getInstance(NewsChecker.class).shutdown();
api.getPlayerLink().stop(); api.getPlayerLink().stop();
return true; return true;
} }

View File

@@ -70,7 +70,7 @@ public class HandshakeDataImpl implements HandshakeData {
int usernameLength = Math.min(bedrockData.getUsername().length(), 16 - prefix.length()); int usernameLength = Math.min(bedrockData.getUsername().length(), 16 - prefix.length());
javaUsername = prefix + bedrockData.getUsername().substring(0, usernameLength); javaUsername = prefix + bedrockData.getUsername().substring(0, usernameLength);
if (config.isReplaceSpaces()) { if (config.isReplaceSpaces()) {
javaUsername = javaUsername.replaceAll(" ", "_"); javaUsername = javaUsername.replace(" ", "_");
} }
javaUniqueId = Utils.getJavaUuid(bedrockData.getXuid()); javaUniqueId = Utils.getJavaUuid(bedrockData.getXuid());

View File

@@ -35,20 +35,20 @@ import cloud.commandframework.context.CommandContext;
import com.google.inject.Inject; import com.google.inject.Inject;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.link.LinkRequestResult; import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger; 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.FloodgateConfig;
import org.geysermc.floodgate.link.GlobalPlayerLinking; import org.geysermc.floodgate.link.GlobalPlayerLinking;
import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.platform.command.TranslatableMessage; import org.geysermc.floodgate.platform.command.TranslatableMessage;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudience.PlayerAudience; 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.Constants;
import org.geysermc.floodgate.util.Permissions;
@NoArgsConstructor @NoArgsConstructor
public final class LinkAccountCommand implements FloodgateCommand { public final class LinkAccountCommand implements FloodgateCommand {
@@ -60,8 +60,8 @@ public final class LinkAccountCommand implements FloodgateCommand {
return commandManager.commandBuilder("linkaccount", return commandManager.commandBuilder("linkaccount",
ArgumentDescription.of("Link your Java account with your Bedrock account")) ArgumentDescription.of("Link your Java account with your Bedrock account"))
.senderType(PlayerAudience.class) .senderType(PlayerAudience.class)
.permission(Permissions.COMMAND_LINK.get()) .permission(Permission.COMMAND_LINK.get())
.argument(UserAudienceArgument.of("player", true)) .argument(ProfileAudienceArgument.of("player", true))
.argument(StringArgument.optional("code")) .argument(StringArgument.optional("code"))
.handler(this::execute) .handler(this::execute)
.build(); .build();
@@ -90,6 +90,10 @@ public final class LinkAccountCommand implements FloodgateCommand {
return; 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 // when the player is a Bedrock player
if (api.isFloodgatePlayer(sender.uuid())) { if (api.isFloodgatePlayer(sender.uuid())) {
if (!context.contains("code")) { if (!context.contains("code")) {
@@ -97,8 +101,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
return; return;
} }
UserAudience targetUser = context.get("player");
String targetName = targetUser.username();
String code = context.get("code"); String code = context.get("code");
link.verifyLinkRequest(sender.uuid(), targetName, sender.username(), 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); sender.disconnect(Message.LINK_REQUEST_COMPLETED, targetName);
break; break;
default: default:
sender.disconnect(Component.text("Invalid account linking result")); sender.disconnect("Invalid account linking result");
break; break;
} }
}); });
@@ -137,9 +139,6 @@ public final class LinkAccountCommand implements FloodgateCommand {
return; return;
} }
UserAudience targetUser = context.get("player");
String targetName = targetUser.username();
link.createLinkRequest(sender.uuid(), sender.username(), targetName) link.createLinkRequest(sender.uuid(), sender.username(), targetName)
.whenComplete((result, throwable) -> { .whenComplete((result, throwable) -> {
if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) { if (throwable != null || result == LinkRequestResult.UNKNOWN_ERROR) {

View File

@@ -28,7 +28,6 @@ package org.geysermc.floodgate.command;
import cloud.commandframework.Command; import cloud.commandframework.Command;
import cloud.commandframework.CommandManager; import cloud.commandframework.CommandManager;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.FloodgateCommand;
@@ -47,7 +46,7 @@ public class TestCommand implements FloodgateCommand {
@Override @Override
public void execute(CommandContext<UserAudience> context) { public void execute(CommandContext<UserAudience> context) {
int players = FloodgateApi.getInstance().getPlayers().size(); int players = FloodgateApi.getInstance().getPlayers().size();
context.getSender().sendMessage(Component.text(players)); context.getSender().sendMessage(String.valueOf(players));
} }
@Override @Override

View File

@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.TranslatableMessage;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.player.UserAudience.PlayerAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.Permissions; import org.geysermc.floodgate.command.util.Permission;
@NoArgsConstructor @NoArgsConstructor
public final class UnlinkAccountCommand implements FloodgateCommand { public final class UnlinkAccountCommand implements FloodgateCommand {
@@ -54,7 +54,7 @@ public final class UnlinkAccountCommand implements FloodgateCommand {
return commandManager.commandBuilder("unlinkaccount", return commandManager.commandBuilder("unlinkaccount",
ArgumentDescription.of("Unlink your Java account from your Bedrock account")) ArgumentDescription.of("Unlink your Java account from your Bedrock account"))
.senderType(PlayerAudience.class) .senderType(PlayerAudience.class)
.permission(Permissions.COMMAND_UNLINK.get()) .permission(Permission.COMMAND_UNLINK.get())
.handler(this::execute) .handler(this::execute)
.build(); .build();
} }

View File

@@ -38,17 +38,18 @@ import java.util.UUID;
import lombok.Getter; import lombok.Getter;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.platform.command.TranslatableMessage; 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.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument; import org.geysermc.floodgate.player.audience.ProfileAudience;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.player.audience.ProfileAudienceArgument;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils; import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.Permissions;
public class WhitelistCommand implements FloodgateCommand { public class WhitelistCommand implements FloodgateCommand {
@Inject private FloodgateConfig config; @Inject private FloodgateConfig config;
@@ -58,25 +59,25 @@ public class WhitelistCommand implements FloodgateCommand {
public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) { public Command<UserAudience> buildCommand(CommandManager<UserAudience> commandManager) {
Command.Builder<UserAudience> builder = commandManager.commandBuilder("fwhitelist", Command.Builder<UserAudience> builder = commandManager.commandBuilder("fwhitelist",
ArgumentDescription.of("Easy way to whitelist Bedrock players")) ArgumentDescription.of("Easy way to whitelist Bedrock players"))
.permission(Permissions.COMMAND_WHITELIST.get()); .permission(Permission.COMMAND_WHITELIST.get());
commandManager.command(builder commandManager.command(builder
.literal("add", "a") .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))); .handler(context -> performCommand(context, true)));
return builder return builder
.literal("remove", "r") .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)) .handler(context -> performCommand(context, false))
.build(); .build();
} }
public void performCommand(CommandContext<UserAudience> context, boolean add) { public void performCommand(CommandContext<UserAudience> context, boolean add) {
UserAudience sender = context.getSender(); UserAudience sender = context.getSender();
UserAudience player = context.get("player"); ProfileAudience profile = context.get("player");
UUID uuid = player.uuid(); UUID uuid = profile.uuid();
String name = player.username(); String name = profile.username();
if (name == null && uuid == null) { if (name == null && uuid == null) {
sender.sendMessage(Message.UNEXPECTED_ERROR); sender.sendMessage(Message.UNEXPECTED_ERROR);

View File

@@ -35,9 +35,9 @@ import cloud.commandframework.context.CommandContext;
import java.util.Locale; import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.FloodgateCommand; import org.geysermc.floodgate.platform.command.FloodgateCommand;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.util.Permissions;
public final class MainCommand implements FloodgateCommand { public final class MainCommand implements FloodgateCommand {
@Override @Override
@@ -46,12 +46,13 @@ public final class MainCommand implements FloodgateCommand {
"floodgate", "floodgate",
ArgumentDescription.of("A set of Floodgate related actions in one command")) ArgumentDescription.of("A set of Floodgate related actions in one command"))
.senderType(UserAudience.class) .senderType(UserAudience.class)
.permission(Permissions.COMMAND_MAIN.get()) .permission(Permission.COMMAND_MAIN.get())
.handler(this::execute); .handler(this::execute);
for (SubCommand subCommand : SubCommand.VALUES) { for (SubCommand subCommand : SubCommand.VALUES) {
commandManager.command(builder commandManager.command(builder
.literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description) .literal(subCommand.name().toLowerCase(Locale.ROOT), subCommand.description)
.permission(subCommand.permission.get())
.handler(subCommand.executor::accept) .handler(subCommand.executor::accept)
); );
} }
@@ -65,11 +66,13 @@ public final class MainCommand implements FloodgateCommand {
StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n"); StringBuilder helpMessage = new StringBuilder("Available subcommands are:\n");
for (SubCommand subCommand : SubCommand.VALUES) { for (SubCommand subCommand : SubCommand.VALUES) {
if (context.getSender().hasPermission(subCommand.permission.get())) {
helpMessage.append('\n').append(COLOR_CHAR).append('b') helpMessage.append('\n').append(COLOR_CHAR).append('b')
.append(subCommand.name().toLowerCase(Locale.ROOT)) .append(subCommand.name().toLowerCase(Locale.ROOT))
.append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7') .append(COLOR_CHAR).append("f - ").append(COLOR_CHAR).append('7')
.append(subCommand.description); .append(subCommand.description);
} }
}
context.getSender().sendMessage(helpMessage.toString()); context.getSender().sendMessage(helpMessage.toString());
} }
@@ -77,11 +80,12 @@ public final class MainCommand implements FloodgateCommand {
@RequiredArgsConstructor @RequiredArgsConstructor
enum SubCommand { enum SubCommand {
FIREWALL("Check if your outgoing firewall allows Floodgate to work properly", FIREWALL("Check if your outgoing firewall allows Floodgate to work properly",
FirewallCheckSubcommand::executeFirewall); Permission.COMMAND_MAIN_FIREWALL, FirewallCheckSubcommand::executeFirewall);
static final SubCommand[] VALUES = values(); static final SubCommand[] VALUES = values();
final String description; final String description;
final Permission permission;
final Consumer<CommandContext<UserAudience>> executor; final Consumer<CommandContext<UserAudience>> executor;
} }
} }

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -23,26 +23,27 @@
* @link https://github.com/GeyserMC/Floodgate * @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.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Key; import java.security.Key;
import java.util.UUID;
import lombok.Getter;
import lombok.RequiredArgsConstructor; 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.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.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.crypto.KeyProducer;
@Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public final class ConfigLoader { public final class ConfigLoader {
private final Path dataFolder; private final Path dataFolder;
private final Class<? extends FloodgateConfig> configClass; private final Class<? extends FloodgateConfig> configClass;
private final DefaultConfigHandler configCreator;
private final ConfigUpdater updater;
private final KeyProducer keyProducer; private final KeyProducer keyProducer;
private final FloodgateCipher cipher; private final FloodgateCipher cipher;
@@ -51,61 +52,42 @@ public final class ConfigLoader {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends FloodgateConfig> T load() { public <T extends FloodgateConfig> T load() {
Path configPath = dataFolder.resolve("config.yml"); String templateFile = "config.yml";
if (ProxyFloodgateConfig.class.isAssignableFrom(configClass)) {
String defaultConfigName = "config.yml"; templateFile = "proxy-" + templateFile;
boolean proxy = ProxyFloodgateConfig.class.isAssignableFrom(configClass);
if (proxy) {
defaultConfigName = "proxy-" + defaultConfigName;
} }
boolean newConfig = !Files.exists(configPath); //todo old Floodgate logged a message when version = 0 and it generated a new key.
if (newConfig) { // Might be nice to allow you to run a function for a specific version.
try {
configCreator.createDefaultConfig(defaultConfigName, configPath);
} catch (Exception exception) {
logger.error("Error while creating config", exception);
}
}
T configInstance; // it would also be nice to have sections in versionBuilder so that you don't have to
try { // provide the path all the time
// check and update if the config is outdated
if (!newConfig) {
updater.update(this, defaultConfigName);
}
FloodgateConfig config = ConfigInitializer.initializeFrom( ConfigUtilities utilities =
Files.newInputStream(configPath), configClass); 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();
try { try {
configInstance = (T) config; return (T) utilities.executeOn(configClass);
} catch (ClassCastException exception) { } catch (Throwable throwable) {
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);
throw new RuntimeException( 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) { public void generateKey(Path keyPath) {

View File

@@ -25,15 +25,20 @@
package org.geysermc.floodgate.config; 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.security.Key;
import lombok.Getter; 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 * 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. * addition to the global configuration like {@link ProxyFloodgateConfig} for the proxies.
*/ */
@Getter @Getter
public class FloodgateConfig { public class FloodgateConfig implements GenericPostInitializeCallback<ConfigLoader> {
private String keyFileName; private String keyFileName;
private String usernamePrefix; private String usernamePrefix;
private boolean replaceSpaces; private boolean replaceSpaces;
@@ -42,22 +47,36 @@ public class FloodgateConfig {
private DisconnectMessages disconnect; private DisconnectMessages disconnect;
private PlayerLinkConfig playerLink; private PlayerLinkConfig playerLink;
private MetricsConfig metrics;
private boolean debug; private boolean debug;
private int configVersion; private int configVersion;
private Key key; private Key key;
public void setKey(Key key) {
if (this.key == null) {
this.key = key;
}
}
public boolean isProxy() { public boolean isProxy() {
return this instanceof ProxyFloodgateConfig; 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 @Getter
public static class DisconnectMessages { public static class DisconnectMessages {
private String invalidKey; private String invalidKey;
@@ -68,10 +87,16 @@ public class FloodgateConfig {
public static class PlayerLinkConfig { public static class PlayerLinkConfig {
private boolean enabled; private boolean enabled;
private boolean requireLink; private boolean requireLink;
private boolean enableOwnLinking = false; private boolean enableOwnLinking;
private boolean allowed; private boolean allowed;
private long linkCodeTimeout; private long linkCodeTimeout;
private String type; private String type;
private boolean enableGlobalLinking; private boolean enableGlobalLinking;
} }
@Getter
public static class MetricsConfig {
private boolean enabled;
private String uuid;
}
} }

View File

@@ -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<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) {
Map<String, Property> properties = new LinkedHashMap<>();
getPropertiesFromClass(type, FloodgateConfig.class, properties);
return properties;
}
private void getPropertiesFromClass(
Class<?> type,
Class<?> stopAfter,
Map<String, Property> 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 extends FloodgateConfig> T initializeFrom(
InputStream dataStream,
Class<T> configClass) {
return YAML.loadAs(dataStream, configClass);
}
}

View File

@@ -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<String> configLines = loadDefaultConfig(defaultConfigLocation);
// writing the new config file
Files.write(configPath, configLines);
}
public List<String> loadDefaultConfig(String defaultConfigLocation)
throws IOException {
List<String> lines = Utils.readAllLines(defaultConfigLocation);
List<String> configLines = new ArrayList<>();
String parentConfig = null;
List<String> 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;
}
}

View File

@@ -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<String, Object> currentVersion,
Map<String, String> renames,
String defaultConfigLocation)
throws IOException {
List<String> notFound = new ArrayList<>();
List<String> newConfig = defaultConfigHandler.loadDefaultConfig(defaultConfigLocation);
String spaces = "";
Map<String, Object> 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<String, Object>) 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());
}
}
}

View File

@@ -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<String, Object> 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<String, String> 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);
}
}
}

View File

@@ -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()} * if {@link InjectorAddon#shouldInject()}
* *
* @param channel the channel that was injected * @param channel the channel that was injected

View File

@@ -40,12 +40,9 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ConfigLoader;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; 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.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.Base64Topping;
@@ -105,27 +102,10 @@ public class CommonModule extends AbstractModule {
@Singleton @Singleton
public ConfigLoader configLoader( public ConfigLoader configLoader(
@Named("configClass") Class<? extends FloodgateConfig> configClass, @Named("configClass") Class<? extends FloodgateConfig> configClass,
DefaultConfigHandler defaultConfigHandler,
ConfigUpdater configUpdater,
KeyProducer producer, KeyProducer producer,
FloodgateCipher cipher, FloodgateCipher cipher,
FloodgateLogger logger) { FloodgateLogger logger) {
return new ConfigLoader(dataDirectory, configClass, defaultConfigHandler, configUpdater, return new ConfigLoader(dataDirectory, configClass, producer, cipher, logger);
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);
} }
@Provides @Provides

View File

@@ -43,7 +43,7 @@ import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpUtils; import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse; import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.Permissions; import org.geysermc.floodgate.command.util.Permission;
public class NewsChecker { public class NewsChecker {
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
@@ -72,8 +72,6 @@ public class NewsChecker {
} }
private void checkNews() { private void checkNews() {
// todo also check news for the downloaded database types
HttpResponse<JsonArray> response = HttpResponse<JsonArray> response =
HttpUtils.getSilent( HttpUtils.getSilent(
Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME, Constants.NEWS_OVERVIEW_URL + Constants.NEWS_PROJECT_NAME,
@@ -121,14 +119,14 @@ public class NewsChecker {
return; 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(); String message = Constants.COLOR_CHAR + "a " + news.getMessage();
commandUtil.sendMessage(player, message); commandUtil.sendMessage(player, message);
} }
break; break;
case BROADCAST_TO_OPERATORS: case BROADCAST_TO_OPERATORS:
Collection<Object> onlinePlayers = commandUtil.getOnlinePlayersWithPermission( Collection<Object> onlinePlayers = commandUtil.getOnlinePlayersWithPermission(
Permissions.NEWS_RECEIVE.get() Permission.NEWS_RECEIVE.get()
); );
for (Object onlinePlayer : onlinePlayers) { for (Object onlinePlayer : onlinePlayers) {
@@ -182,7 +180,8 @@ public class NewsChecker {
schedule(delayMs > 0 ? delayMs : 0); schedule(delayMs > 0 ? delayMs : 0);
break; break;
case CONFIG_SPECIFIC: 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; break;
} }
activeNewsItems.put(item.getId(), item); activeNewsItems.put(item.getId(), item);

View File

@@ -25,30 +25,118 @@
package org.geysermc.floodgate.platform.command; 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.Collection;
import java.util.List;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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.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; import org.geysermc.floodgate.util.Utils;
/** /**
* An interface used across all Floodgate platforms to simple stuff in commands like kicking players * 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. * and sending player messages independent of the Floodgate platform implementation.
*/ */
public interface CommandUtil { @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@NonNull UserAudience getAudience(final @NonNull Object source); 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<String> getOnlineUsernames(final @NonNull PlayerType limitTo); protected abstract Collection<?> getOnlinePlayers();
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
Collection<?> players = getOnlinePlayers();
Collection<String> 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. * Checks if the given player has the given permission.
@@ -57,7 +145,7 @@ public interface CommandUtil {
* @param permission the permission to check * @param permission the permission to check
* @return true or false depending on if the player has the permission * @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. * Get all online players with the given permission.
@@ -65,17 +153,15 @@ public interface CommandUtil {
* @param permission the permission to check * @param permission the permission to check
* @return a list of online players that have the given permission * @return a list of online players that have the given permission
*/ */
Collection<Object> getOnlinePlayersWithPermission(String permission); public Collection<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> players = new ArrayList<>();
/** for (Object player : getOnlinePlayers()) {
* Send a message to the specified target, no matter what platform Floodgate is running on. if (hasPermission(player, permission)) {
* players.add(player);
* @param target the player that should receive the message }
* @param message the command message }
* @param locale the locale of the player return players;
* @param args the arguments }
*/
void sendMessage(Object target, String locale, TranslatableMessage message, Object... args);
/** /**
* Sends a raw message to the specified target, no matter what platform Floodgate is running * 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 target the player that should receive the message
* @param message 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...)} * Kicks the given player using the given message as the kick reason.
* except it kicks the player using the given message as the kick reason.
* *
* @param player the player that should be kicked * @param player the player that should be kicked
* @param message the command message * @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. * 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. * @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. * 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); UUID uuid = Utils.getJavaUuid(xuid);
return whitelistPlayer(uuid, username); 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. * @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. * 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; 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 * @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. * 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); UUID uuid = Utils.getJavaUuid(xuid);
return removePlayerFromWhitelist(uuid, username); 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 * @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. * 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; return false;
} }
} }

View File

@@ -28,8 +28,8 @@ package org.geysermc.floodgate.platform.command;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
/** /**
* TranslatableMessage is the interface for a message that can be translated. * TranslatableMessage is the interface for a message that can be translated. Messages are generally
* Messages are generally implemented using enums. * implemented using enums.
*/ */
public interface TranslatableMessage { public interface TranslatableMessage {
/** /**
@@ -52,7 +52,7 @@ public interface TranslatableMessage {
for (int i = 0; i < translateParts.length; i++) { for (int i = 0; i < translateParts.length; i++) {
builder.append(manager.getString(translateParts[i], locale, args)); builder.append(manager.getString(translateParts[i], locale, args));
if (translateParts.length != i + 1) { if (translateParts.length != i + 1) {
builder.append(" "); builder.append(' ');
} }
} }
return builder.toString(); return builder.toString();

View File

@@ -25,37 +25,26 @@
package org.geysermc.floodgate.platform.util; package org.geysermc.floodgate.platform.util;
import java.util.Collection; import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.command.TranslatableMessage;
public interface PlatformUtils { @RequiredArgsConstructor
public abstract class PlatformUtils {
/** /**
* Send a message to the specified player, no matter what platform Floodgate is running on. * Returns the authentication type used on the platform
*
* @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
*/ */
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 * Returns the Minecraft version the server is based on (or the most recent supported version
* kicks the player. * for proxy platforms)
*
* @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
*/ */
void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args); public abstract String minecraftVersion();
Collection<String> getOnlineUsernames(PlayerType limitTo); public abstract String serverImplementationName();
enum PlayerType { public enum AuthType {
ALL_PLAYERS, ONLINE,
ONLY_BEDROCK, PROXIED,
ONLY_JAVA OFFLINE
} }
} }

View File

@@ -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
}

View File

@@ -35,7 +35,6 @@ import io.netty.util.AttributeKey;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import lombok.AccessLevel; import lombok.AccessLevel;
@@ -96,7 +95,7 @@ public final class FloodgateHandshakeHandler {
String floodgateData = null; String floodgateData = null;
int dataVersion = -1; int dataVersion = -1;
StringBuilder hostnameBuilder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (String value : hostnameItems) { for (String value : hostnameItems) {
int version = FloodgateCipher.version(value); int version = FloodgateCipher.version(value);
if (floodgateData == null && version != -1) { if (floodgateData == null && version != -1) {
@@ -104,10 +103,14 @@ public final class FloodgateHandshakeHandler {
dataVersion = version; dataVersion = version;
continue; continue;
} }
hostnameBuilder.append(value).append('\0');
if (builder.length() > 0) {
builder.append('\0');
} }
// hostname now doesn't have Floodgate data anymore if it had builder.append(value);
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<HandshakeResult> handle( public CompletableFuture<HandshakeResult> handle(
@@ -218,8 +221,6 @@ public final class FloodgateHandshakeHandler {
bedrockData.getVerifyCode()); bedrockData.getVerifyCode());
} }
correctHostname(handshakeData);
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, handshakeData); FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, handshakeData);
api.addPlayer(player); api.addPlayer(player);
@@ -247,30 +248,9 @@ public final class FloodgateHandshakeHandler {
bedrockData, configHolder.get(), null, hostname); bedrockData, configHolder.get(), null, hostname);
handshakeHandlers.callHandshakeHandlers(handshakeData); handshakeHandlers.callHandshakeHandlers(handshakeData);
if (bedrockData != null) {
correctHostname(handshakeData);
}
return new HandshakeResult(resultType, handshakeData, bedrockData, null); 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<Pair<BedrockData, LinkedPlayer>> fetchLinkedPlayer(BedrockData data) { private CompletableFuture<Pair<BedrockData, LinkedPlayer>> fetchLinkedPlayer(BedrockData data) {
if (!api.getPlayerLink().isEnabled()) { if (!api.getPlayerLink().isEnabled()) {
return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null)); return CompletableFuture.completedFuture(new ObjectObjectImmutablePair<>(data, null));

View File

@@ -25,52 +25,80 @@
package org.geysermc.floodgate.player; package org.geysermc.floodgate.player;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.audience.Audience; import lombok.Getter;
import net.kyori.adventure.audience.MessageType; import lombok.experimental.Accessors;
import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; 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.platform.command.TranslatableMessage;
public interface UserAudience extends Identified, Identity, Audience { @Getter @Accessors(fluent = true)
@Override public class UserAudience {
@NonNull UUID uuid(); 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(); public UserAudience(
@NonNull UUID uuid,
@NonNull String locale(); @NonNull String username,
@NonNull String locale,
@NonNull Object source(); @NonNull Object source,
@NonNull CommandUtil commandUtil) {
boolean hasPermission(@NonNull final String permission); this.uuid = Objects.requireNonNull(uuid);
this.username = username;
@Override this.locale = Objects.requireNonNull(locale);
void sendMessage(final @NonNull Identity source, this.source = Objects.requireNonNull(source);
final @NonNull Component message, this.commandUtil = Objects.requireNonNull(commandUtil);
final @NonNull MessageType type);
void sendMessage(TranslatableMessage message, Object... args);
default void sendMessage(String message) {
sendMessage(Component.text(message));
} }
void disconnect(@NonNull final Component reason); public boolean hasPermission(@NonNull String permission) {
return commandUtil.hasPermission(source(), permission);
void disconnect(TranslatableMessage message, Object... args);
@Override
default @NonNull Identity identity() {
return this;
} }
interface PlayerAudience extends UserAudience { public void sendMessage(String message) {
boolean online(); 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);
}
} }
} }

View File

@@ -23,23 +23,20 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.util; package org.geysermc.floodgate.player.audience;
public enum Permissions { import java.util.UUID;
COMMAND_MAIN("floodgate.command.floodgate"), import lombok.Getter;
COMMAND_LINK("floodgate.command.linkaccount"), import lombok.experimental.Accessors;
COMMAND_UNLINK("floodgate.command.unlinkaccount"), import org.checkerframework.checker.nullness.qual.Nullable;
COMMAND_WHITELIST("floodgate.command.fwhitelist"),
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; public ProfileAudience(@Nullable UUID uuid, @Nullable String username) {
this.uuid = uuid;
Permissions(String permission) { this.username = username;
this.permission = permission;
}
public String get() {
return permission;
} }
} }

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @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.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParseResult;
@@ -37,62 +37,58 @@ import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.platform.command.CommandUtil; 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<UserAudience, UserAudience> { public class ProfileAudienceArgument extends CommandArgument<UserAudience, ProfileAudience> {
private UserAudienceArgument(final @NonNull String name, final UserAudienceParser parser) { private ProfileAudienceArgument(@NonNull String name, ProfileAudienceParser parser) {
super(true, name, parser, UserAudience.class); super(true, name, parser, ProfileAudience.class);
} }
public static UserAudienceArgument of( public static ProfileAudienceArgument of(
final String name, String name,
final boolean allowUuid, boolean allowUuid,
final boolean allowOffline, boolean allowOffline,
final PlayerType limitTo) { PlayerType limitTo) {
return new UserAudienceArgument(name, return new ProfileAudienceArgument(name,
new UserAudienceParser(allowUuid, allowOffline, limitTo)); new ProfileAudienceParser(allowUuid, allowOffline, limitTo));
} }
public static UserAudienceArgument of( public static ProfileAudienceArgument of(
final String name, String name,
final boolean allowOffline, boolean allowOffline,
final PlayerType limitTo) { PlayerType limitTo) {
return of(name, false, allowOffline, 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); 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); return of(name, allowUuid, false, PlayerType.ALL_PLAYERS);
} }
public static CommandArgument<UserAudience, UserAudience> ofOnline(final String name) { public static CommandArgument<UserAudience, ProfileAudience> ofOnline(String name) {
return of(name, false, false, PlayerType.ALL_PLAYERS); 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); return of(name, false, allowOffline, PlayerType.ALL_PLAYERS);
} }
public enum PlayerType {
ALL_PLAYERS,
ONLY_BEDROCK,
ONLY_JAVA
}
@RequiredArgsConstructor @RequiredArgsConstructor
public static final class UserAudienceParser public static final class ProfileAudienceParser
implements ArgumentParser<UserAudience, UserAudience> { implements ArgumentParser<UserAudience, ProfileAudience> {
private final boolean allowUuid; private final boolean allowUuid;
private final boolean allowOffline; private final boolean allowOffline;
private final PlayerType limitTo; private final PlayerType limitTo;
@Override @Override
public @NonNull ArgumentParseResult<UserAudience> parse( public @NonNull ArgumentParseResult<ProfileAudience> parse(
final @NonNull CommandContext<@NonNull UserAudience> commandContext, @NonNull CommandContext<@NonNull UserAudience> commandContext,
final @NonNull Queue<@NonNull String> inputQueue) { @NonNull Queue<@NonNull String> inputQueue) {
CommandUtil commandUtil = commandContext.get("CommandUtil"); CommandUtil commandUtil = commandContext.get("CommandUtil");
String input = inputQueue.poll(); String input = inputQueue.poll();
@@ -111,7 +107,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
StringBuilder builder = new StringBuilder(input); StringBuilder builder = new StringBuilder(input);
while (!inputQueue.isEmpty()) { while (!inputQueue.isEmpty()) {
String string = inputQueue.remove(); String string = inputQueue.remove();
builder.append(" ").append(string); builder.append(' ').append(string);
if (string.endsWith("\"")) { if (string.endsWith("\"")) {
break; break;
} }
@@ -129,7 +125,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
} }
} }
UserAudience userAudience; ProfileAudience profileAudience;
if (input.length() > 16) { if (input.length() > 16) {
// This must be a UUID. // This must be a UUID.
@@ -146,46 +142,39 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
try { try {
// We only want to make sure the UUID is valid here. // We only want to make sure the UUID is valid here.
final UUID uuid = UUID.fromString(input); Object player = commandUtil.getPlayerByUuid(UUID.fromString(input), limitTo);
userAudience = commandUtil.getAudienceByUuid(uuid); profileAudience = commandUtil.getProfileAudience(player, allowOffline);
if (userAudience == null && allowOffline) {
userAudience = commandUtil.getOfflineAudienceByUuid(uuid);
}
} catch (final IllegalArgumentException ignored) { } catch (final IllegalArgumentException ignored) {
return ArgumentParseResult.failure( return ArgumentParseResult.failure(
new InvalidPlayerIdentifierException("Invalid UUID '" + input + "'")); new InvalidPlayerIdentifierException("Invalid UUID '" + input + "'"));
} }
} else { } else {
// This is a username. // This is a username.
userAudience = commandUtil.getAudienceByUsername(input); Object player = commandUtil.getPlayerByUsername(input, limitTo);
profileAudience = commandUtil.getProfileAudience(player, allowOffline);
if (userAudience == null && allowOffline) {
userAudience = commandUtil.getOfflineAudienceByUsername(input);
}
} }
if (userAudience == null) { if (profileAudience == null) {
return ArgumentParseResult.failure( return ArgumentParseResult.failure(
new InvalidPlayerIdentifierException("Invalid player '" + input + "'")); new InvalidPlayerIdentifierException("Invalid player '" + input + "'"));
} }
return ArgumentParseResult.success(userAudience); return ArgumentParseResult.success(profileAudience);
} }
@Override @Override
public @NonNull List<String> suggestions( public @NonNull List<String> suggestions(
final @NonNull CommandContext<UserAudience> commandContext, @NonNull CommandContext<UserAudience> commandContext,
final @NonNull String input) { @NonNull String input) {
final CommandUtil commandUtil = commandContext.get("CommandUtil"); CommandUtil commandUtil = commandContext.get("CommandUtil");
final String trimmedInput = input.trim(); String trimmedInput = input.trim();
if (trimmedInput.isEmpty()) { if (trimmedInput.isEmpty()) {
return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo)); return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo));
} }
final String lowercaseInput = input.toLowerCase(Locale.ROOT); String lowercaseInput = input.toLowerCase(Locale.ROOT);
final ImmutableList.Builder<String> builder = ImmutableList.builder(); ImmutableList.Builder<String> builder = ImmutableList.builder();
for (final String player : commandUtil.getOnlineUsernames(limitTo)) { for (final String player : commandUtil.getOnlineUsernames(limitTo)) {
if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) { if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) {
@@ -205,7 +194,7 @@ public final class UserAudienceArgument extends CommandArgument<UserAudience, Us
public static final class InvalidPlayerIdentifierException extends IllegalArgumentException { public static final class InvalidPlayerIdentifierException extends IllegalArgumentException {
private static final long serialVersionUID = -6500019324607183855L; private static final long serialVersionUID = -6500019324607183855L;
public InvalidPlayerIdentifierException(final @NonNull String message) { public InvalidPlayerIdentifierException(@NonNull String message) {
super(message); super(message);
} }

View File

@@ -26,8 +26,10 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
public final class Constants { public final class Constants {
public static final String VERSION = "${floodgateVersion}";
public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}"); public static final int BUILD_NUMBER = Integer.parseInt("${buildNumber}");
public static final String GIT_BRANCH = "${branch}"; public static final String GIT_BRANCH = "${branch}";
public static final int METRICS_ID = 14649;
public static final char COLOR_CHAR = '§'; public static final char COLOR_CHAR = '§';

View File

@@ -0,0 +1,147 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Named;
import org.bstats.MetricsBase;
import org.bstats.charts.DrilldownPie;
import org.bstats.charts.SimplePie;
import org.bstats.charts.SingleLineChart;
import org.bstats.json.JsonObjectBuilder;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.MetricsConfig;
import org.geysermc.floodgate.platform.util.PlatformUtils;
public final class Metrics {
private final MetricsBase metricsBase;
@Inject
Metrics(FloodgateConfig config, PlatformUtils platformUtils, FloodgateApi api,
@Named("implementationName") String implementationName, FloodgateLogger logger) {
MetricsConfig metricsConfig = config.getMetrics();
metricsBase = new MetricsBase(
"server-implementation",
metricsConfig.getUuid(),
Constants.METRICS_ID,
metricsConfig.isEnabled(),
this::appendPlatformData,
jsonObjectBuilder -> { /* 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<String, Map<String, Integer>> map = new HashMap<>();
String javaVersion = System.getProperty("java.version");
Map<String, Integer> 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());
}
}

View File

@@ -269,6 +269,11 @@ public final class ReflectionUtils {
return (T) getValue(instance, getField(instance.getClass(), fieldName)); return (T) getValue(instance, getField(instance.getClass(), fieldName));
} }
@Nullable
public static <T> 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.<br> * Set the value of a field. This method make the field accessible and then sets the value.<br>
* This method doesn't throw an exception when failed, but it'll log the error to the console. * This method doesn't throw an exception when failed, but it'll log the error to the console.

View File

@@ -58,5 +58,9 @@ player-link:
# you have limited internet access. # you have limited internet access.
enable-global-linking: true enable-global-linking: true
metrics:
enabled: true
uuid: ${metrics.uuid}
# Do not change this # Do not change this
config-version: 2 config-version: 3

View File

@@ -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")

View File

@@ -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<Document> linkedPlayer;
private MongoCollection<Document> 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<LinkedPlayer> getLinkedPlayer(@NonNull UUID bedrockId) {
return CompletableFuture.supplyAsync(() -> {
try {
Bson filter = Filters.eq("bedrockId", uuidToBytes(bedrockId));
try (MongoCursor<Document> 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<Boolean> 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<Document> 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<Void> linkPlayer(
@NonNull UUID bedrockId,
@NonNull UUID javaId,
@NonNull String javaUsername) {
return CompletableFuture.runAsync(
() -> linkPlayer0(bedrockId, javaId, javaUsername),
getExecutorService());
}
private void linkPlayer0(UUID bedrockId, UUID javaId, String javaUsername) {
try {
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<Void> 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<String> createLinkRequest(
@NonNull UUID javaId,
@NonNull String javaUsername,
@NonNull String bedrockUsername) {
return CompletableFuture.supplyAsync(() -> {
String linkCode = createCode();
createLinkRequest0(javaUsername, javaId, linkCode, bedrockUsername);
return linkCode;
}, getExecutorService());
}
private void createLinkRequest0(
String javaUsername,
UUID javaId,
String linkCode,
String bedrockUsername) {
try {
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<LinkRequestResult> verifyLinkRequest(
@NonNull UUID bedrockId,
@NonNull String javaUsername,
@NonNull String bedrockUsername,
@NonNull String code) {
return CompletableFuture.supplyAsync(() -> {
LinkRequest request = getLinkRequest0(javaUsername);
if (request == null || !isRequestedPlayer(request, bedrockId)) {
return LinkRequestResult.NO_LINK_REQUESTED;
}
if (!request.getLinkCode().equals(code)) {
return LinkRequestResult.INVALID_CODE;
}
// link request can be removed. Doesn't matter if the request is expired or not
removeLinkRequest(javaUsername);
if (request.isExpired(getVerifyLinkTimeout())) {
return LinkRequestResult.REQUEST_EXPIRED;
}
linkPlayer0(bedrockId, request.getJavaUniqueId(), javaUsername);
return LinkRequestResult.LINK_COMPLETED;
}, getExecutorService());
}
private LinkRequest getLinkRequest0(String javaUsername) {
try {
Bson filter = Filters.eq("javaUsername", javaUsername);
try (MongoCursor<Document> 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);
}
}

View File

@@ -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 = "";
}

View File

@@ -0,0 +1,4 @@
{
"mainClass": "org.geysermc.floodgate.database.MongoDbDatabase",
"config": "mongo.yml"
}

View File

@@ -0,0 +1,5 @@
hostname: "localhost"
database: "floodgate"
username: "floodgate"
password: ""
mongouri: ""

View File

@@ -10,10 +10,6 @@
<!-- UnusedPrivateMethod --> <!-- UnusedPrivateMethod -->
<exclude-pattern>.*/CommonPlayerLink.*</exclude-pattern> <exclude-pattern>.*/CommonPlayerLink.*</exclude-pattern>
<!-- NullAssignment -->
<exclude-pattern>.*/DefaultConfigHandler.*</exclude-pattern>
<!-- NullAssignment -->
<exclude-pattern>.*/ConfigFileUpdater.*</exclude-pattern>
<!-- RedundantFieldInitializer --> <!-- RedundantFieldInitializer -->
<exclude-pattern>.*/FloodgateConfig.*</exclude-pattern> <exclude-pattern>.*/FloodgateConfig.*</exclude-pattern>
<!-- CloseResource, there is no shutdown event and it has to load classes on the fly --> <!-- CloseResource, there is no shutdown event and it has to load classes on the fly -->

View File

@@ -3,6 +3,8 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
// mavenLocal()
// Geyser, Cumulus etc. // Geyser, Cumulus etc.
maven("https://repo.opencollab.dev/maven-releases") { maven("https://repo.opencollab.dev/maven-releases") {
mavenContent { releasesOnly() } mavenContent { releasesOnly() }
@@ -42,8 +44,6 @@ pluginManagement {
} }
plugins { plugins {
id("net.kyori.blossom") version "1.2.0" id("net.kyori.blossom") version "1.2.0"
id("net.kyori.indra")
id("net.kyori.indra.git")
} }
includeBuild("build-logic") includeBuild("build-logic")
} }
@@ -57,5 +57,7 @@ include(":spigot")
include(":velocity") include(":velocity")
include(":sqlite") include(":sqlite")
include(":mysql") include(":mysql")
include(":mongo")
project(":sqlite").projectDir = file("database/sqlite") project(":sqlite").projectDir = file("database/sqlite")
project(":mysql").projectDir = file("database/mysql") project(":mysql").projectDir = file("database/mysql")
project(":mongo").projectDir = file("database/mongo")

View File

@@ -5,12 +5,9 @@ var gsonVersion = "2.8.5"
dependencies { dependencies {
api(projects.core) api(projects.core)
implementation("cloud.commandframework", "cloud-bukkit", Versions.cloudVersion)
// hack to make pre 1.12 work // hack to make pre 1.12 work
implementation("com.google.guava", "guava", guavaVersion) 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") relocate("com.google.inject")
@@ -24,7 +21,7 @@ relocate("com.google.guava")
relocate("it.unimi") relocate("it.unimi")
// these dependencies are already present on the platform // 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("com.mojang", "authlib", authlibVersion)
provided("io.netty", "netty-transport", Versions.nettyVersion) provided("io.netty", "netty-transport", Versions.nettyVersion)
provided("io.netty", "netty-codec", Versions.nettyVersion) provided("io.netty", "netty-codec", Versions.nettyVersion)

View File

@@ -28,13 +28,17 @@ package org.geysermc.floodgate;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.module.PaperListenerModule;
import org.geysermc.floodgate.module.PluginMessageModule; import org.geysermc.floodgate.module.PluginMessageModule;
import org.geysermc.floodgate.module.ServerCommonModule; import org.geysermc.floodgate.module.ServerCommonModule;
import org.geysermc.floodgate.module.SpigotAddonModule; import org.geysermc.floodgate.module.SpigotAddonModule;
import org.geysermc.floodgate.module.SpigotCommandModule; import org.geysermc.floodgate.module.SpigotCommandModule;
import org.geysermc.floodgate.module.SpigotListenerModule; import org.geysermc.floodgate.module.SpigotListenerModule;
import org.geysermc.floodgate.module.SpigotPlatformModule; 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.SpigotProtocolSupportHandler;
import org.geysermc.floodgate.util.SpigotProtocolSupportListener; import org.geysermc.floodgate.util.SpigotProtocolSupportListener;
@@ -59,13 +63,20 @@ public final class SpigotPlugin extends JavaPlugin {
@Override @Override
public void onEnable() { public void onEnable() {
boolean usePaperListener = ReflectionUtils.getClassSilently(
"com.destroystokyo.paper.event.profile.PreFillProfileEvent") != null;
platform.enable( platform.enable(
new SpigotCommandModule(this), new SpigotCommandModule(this),
new SpigotListenerModule(),
new SpigotAddonModule(), 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) // add ProtocolSupport support (hack)
if (getServer().getPluginManager().getPlugin("ProtocolSupport") != null) { if (getServer().getPluginManager().getPlugin("ProtocolSupport") != null) {
injector.getInstance(SpigotProtocolSupportHandler.class); injector.getInstance(SpigotProtocolSupportHandler.class);

View File

@@ -42,6 +42,7 @@ import org.geysermc.floodgate.util.ProxyUtils;
public final class SpigotDataHandler extends CommonDataHandler { public final class SpigotDataHandler extends CommonDataHandler {
private Object networkManager; private Object networkManager;
private FloodgatePlayer player; private FloodgatePlayer player;
private boolean proxyData;
public SpigotDataHandler( public SpigotDataHandler(
FloodgateHandshakeHandler handshakeHandler, FloodgateHandshakeHandler handshakeHandler,
@@ -52,7 +53,6 @@ public final class SpigotDataHandler extends CommonDataHandler {
@Override @Override
protected void setNewIp(Channel channel, InetSocketAddress newIp) { protected void setNewIp(Channel channel, InetSocketAddress newIp) {
//todo the socket address will be overridden when bungeeData is true
setValue(networkManager, ClassNames.SOCKET_ADDRESS, newIp); 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, // the server will do all the work if BungeeCord mode is enabled,
// otherwise we have to help the server. // 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) // Use a spoofedUUID for initUUID (just like Bungeecord)
setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId()); 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 @Override
protected boolean shouldCallFireRead(Object queuedPacket) { protected boolean shouldCallFireRead(Object queuedPacket) {
// we have to ignore the 'login start' packet if BungeeCord mode is disabled, // we have to ignore the 'login start' packet,
// otherwise the server might ask the user to login // otherwise the server will ask the user to login
try { try {
if (checkAndHandleLogin(queuedPacket)) { if (checkAndHandleLogin(queuedPacket)) {
return false; return false;
@@ -136,6 +138,16 @@ public final class SpigotDataHandler extends CommonDataHandler {
return true; 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 // set the player his GameProfile, we can't change the username without this
GameProfile gameProfile = new GameProfile( GameProfile gameProfile = new GameProfile(
player.getCorrectUniqueId(), player.getCorrectUsername() player.getCorrectUniqueId(), player.getCorrectUsername()

View File

@@ -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<ProfileProperty> properties = new HashSet<>(event.getPlayerProfile().getProperties());
properties.add(new ProfileProperty("textures", "", ""));
event.setProperties(properties);
}
}

View File

@@ -35,9 +35,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.SpigotCommandUtil;
public final class SpigotListener implements Listener { public final class SpigotListener implements Listener {
@Inject private SimpleFloodgateApi api; @Inject private SimpleFloodgateApi api;
@@ -68,7 +66,5 @@ public final class SpigotListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId()); api.playerRemoved(event.getPlayer().getUniqueId());
SpigotCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo
} }
} }

View File

@@ -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<ListenerRegister<Listener>>() {}).asEagerSingleton();
}
@Singleton
@ProvidesIntoSet
public Listener paperProfileListener() {
return new PaperProfileListener();
}
}

View File

@@ -32,8 +32,12 @@ import com.google.inject.Provides;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.geysermc.floodgate.SpigotPlugin; import org.geysermc.floodgate.SpigotPlugin;
import org.geysermc.floodgate.command.util.Permission;
import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.player.FloodgateCommandPreprocessor; import org.geysermc.floodgate.player.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
@@ -42,6 +46,12 @@ import org.geysermc.floodgate.player.UserAudience;
public final class SpigotCommandModule extends CommandModule { public final class SpigotCommandModule extends CommandModule {
private final SpigotPlugin plugin; private final SpigotPlugin plugin;
@Override
protected void configure() {
super.configure();
registerPermissions();
}
@Provides @Provides
@Singleton @Singleton
@SneakyThrows @SneakyThrows
@@ -49,10 +59,26 @@ public final class SpigotCommandModule extends CommandModule {
CommandManager<UserAudience> commandManager = new BukkitCommandManager<>( CommandManager<UserAudience> commandManager = new BukkitCommandManager<>(
plugin, plugin,
CommandExecutionCoordinator.simpleCoordinator(), CommandExecutionCoordinator.simpleCoordinator(),
commandUtil::getAudience, commandUtil::getUserAudience,
audience -> (CommandSender) audience.source() audience -> (CommandSender) audience.source()
); );
commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil));
return commandManager; 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
));
}
}
} }

View File

@@ -33,7 +33,7 @@ import org.bukkit.event.Listener;
import org.geysermc.floodgate.listener.SpigotListener; import org.geysermc.floodgate.listener.SpigotListener;
import org.geysermc.floodgate.register.ListenerRegister; import org.geysermc.floodgate.register.ListenerRegister;
public final class SpigotListenerModule extends AbstractModule { public class SpigotListenerModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(new TypeLiteral<ListenerRegister<Listener>>() {}).asEagerSingleton(); bind(new TypeLiteral<ListenerRegister<Listener>>() {}).asEagerSingleton();

View File

@@ -42,6 +42,7 @@ import org.geysermc.floodgate.logger.JavaUtilFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; 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.PluginMessageRegistration;
import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageRegistration; import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageRegistration;
import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageUtils; 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.skin.SkinApplier;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.SpigotCommandUtil; import org.geysermc.floodgate.util.SpigotCommandUtil;
import org.geysermc.floodgate.util.SpigotPlatformUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class SpigotPlatformModule extends AbstractModule { public final class SpigotPlatformModule extends AbstractModule {
private final SpigotPlugin plugin; private final SpigotPlugin plugin;
@Override
protected void configure() {
bind(PlatformUtils.class).to(SpigotPlatformUtils.class);
}
@Provides @Provides
@Singleton @Singleton
public JavaPlugin javaPlugin() { public JavaPlugin javaPlugin() {
@@ -76,10 +83,9 @@ public final class SpigotPlatformModule extends AbstractModule {
public CommandUtil commandUtil( public CommandUtil commandUtil(
FloodgateApi api, FloodgateApi api,
SpigotVersionSpecificMethods versionSpecificMethods, SpigotVersionSpecificMethods versionSpecificMethods,
FloodgateLogger logger,
LanguageManager languageManager) { LanguageManager languageManager) {
return new SpigotCommandUtil(plugin.getServer(), api, versionSpecificMethods, plugin, return new SpigotCommandUtil(
logger, languageManager); languageManager, plugin.getServer(), api, versionSpecificMethods, plugin);
} }
@Provides @Provides

View File

@@ -43,8 +43,6 @@ public class SpigotPluginMessageRegistration implements PluginMessageRegistratio
(channel1, player, message) -> (channel1, player, message) ->
channel.handleServerCall(message, player.getUniqueId(), player.getName())); channel.handleServerCall(message, player.getUniqueId(), player.getName()));
//todo actually do something with the result, lol
messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier()); messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier());
} }
} }

View File

@@ -25,6 +25,7 @@
package org.geysermc.floodgate.util; 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.getFieldOfType;
import static org.geysermc.floodgate.util.ReflectionUtils.getMethod; import static org.geysermc.floodgate.util.ReflectionUtils.getMethod;
@@ -37,6 +38,7 @@ import java.lang.reflect.Method;
import java.net.SocketAddress; import java.net.SocketAddress;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.checkerframework.checker.nullness.qual.Nullable;
@SuppressWarnings("PMD.SystemPrintln") @SuppressWarnings("PMD.SystemPrintln")
public class ClassNames { public class ClassNames {
@@ -57,12 +59,17 @@ public class ClassNames {
public static final Field LOGIN_PROFILE; public static final Field LOGIN_PROFILE;
public static final Field PACKET_LISTENER; 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 GET_PROFILE_METHOD;
public static final Method LOGIN_DISCONNECT; public static final Method LOGIN_DISCONNECT;
public static final Method NETWORK_EXCEPTION_CAUGHT; public static final Method NETWORK_EXCEPTION_CAUGHT;
public static final Method INIT_UUID; public static final Method INIT_UUID;
public static final Method FIRE_LOGIN_EVENTS; public static final Method FIRE_LOGIN_EVENTS;
public static final Field BUNGEE;
static { static {
String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
SPIGOT_MAPPING_PREFIX = "net.minecraft.server." + version; SPIGOT_MAPPING_PREFIX = "net.minecraft.server." + version;
@@ -71,7 +78,7 @@ public class ClassNames {
// SpigotSkinApplier // SpigotSkinApplier
Class<?> craftPlayerClass = ReflectionUtils.getClass( Class<?> craftPlayerClass = ReflectionUtils.getClass(
"org.bukkit.craftbukkit." + version + ".entity.CraftPlayer"); "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"); checkNotNull(GET_PROFILE_METHOD, "Get profile method");
String nmsPackage = SPIGOT_MAPPING_PREFIX + '.'; String nmsPackage = SPIGOT_MAPPING_PREFIX + '.';
@@ -157,6 +164,28 @@ public class ClassNames {
FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents");
checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); 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) { private static Class<?> getClassOrFallBack(String className, String fallbackName) {

View File

@@ -25,45 +25,22 @@
package org.geysermc.floodgate.util; 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") @SuppressWarnings("ConstantConditions")
public final class ProxyUtils { 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() { public static boolean isProxyData() {
return isBungeeData() || isVelocitySupport(); return isBungeeData() || isVelocitySupport();
} }
private static boolean isBungeeData() { private static boolean isBungeeData() {
return ReflectionUtils.getCastedValue(null, IS_BUNGEE_DATA); return ReflectionUtils.castedStaticValue(ClassNames.BUNGEE);
} }
private static boolean isVelocitySupport() { private static boolean isVelocitySupport() {
if (IS_MODERN_FORWARDING == null) { if (ClassNames.PAPER_VELOCITY_SUPPORT == null) {
return false; return false;
} }
return ReflectionUtils.getCastedValue(null, IS_MODERN_FORWARDING); return ReflectionUtils.castedStaticValue(ClassNames.PAPER_VELOCITY_SUPPORT);
} }
} }

View File

@@ -25,44 +25,40 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.checkerframework.checker.nullness.qual.NonNull; 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.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; 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.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.player.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.util.SpigotUserAudience.SpigotConsoleAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.util.SpigotUserAudience.SpigotPlayerAudience;
@RequiredArgsConstructor
public final class SpigotCommandUtil implements CommandUtil {
public static final @NonNull Map<UUID, UserAudience> AUDIENCE_CACHE = new HashMap<>();
private static UserAudience console;
public final class SpigotCommandUtil extends CommandUtil {
private final Server server; private final Server server;
private final FloodgateApi api;
private final SpigotVersionSpecificMethods versionSpecificMethods; private final SpigotVersionSpecificMethods versionSpecificMethods;
private final JavaPlugin plugin; private final JavaPlugin plugin;
private final FloodgateLogger logger; private UserAudience console;
private final LanguageManager manager;
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 @Override
public @NonNull UserAudience getAudience(final @NonNull Object sourceObj) { public @NonNull UserAudience getUserAudience(final @NonNull Object sourceObj) {
if (!(sourceObj instanceof CommandSender)) { if (!(sourceObj instanceof CommandSender)) {
throw new IllegalArgumentException("Source has to be a CommandSender!"); throw new IllegalArgumentException("Source has to be a CommandSender!");
} }
@@ -72,89 +68,47 @@ public final class SpigotCommandUtil implements CommandUtil {
if (console != null) { if (console != null) {
return console; return console;
} }
return console = new SpigotConsoleAudience(source, this); return console = new ConsoleAudience(source, this);
} }
Player player = (Player) source; Player player = (Player) source;
UUID uuid = player.getUniqueId(); UUID uuid = player.getUniqueId();
String username = player.getName();
String locale = versionSpecificMethods.getLocale(player); String locale = versionSpecificMethods.getLocale(player);
return AUDIENCE_CACHE.computeIfAbsent(uuid, return new PlayerAudience(uuid, username, locale, source,this, true);
$ -> new SpigotPlayerAudience(uuid, locale, source, true, this));
} }
@Override @Override
public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { protected String getUsernameFromSource(@NonNull Object source) {
Player player = server.getPlayer(username); return ((Player) source).getName();
return player != null ? getAudience(player) : null;
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { protected UUID getUuidFromSource(@NonNull Object source) {
return new SpigotPlayerAudience(null, username, null, null, false, this); return ((Player) source).getUniqueId();
} }
@Override @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); Player player = server.getPlayer(uuid);
return player != null ? getAudience(player) : null; return player != null ? player : uuid;
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { public Object getPlayerByUsername(@NonNull String username) {
return new SpigotPlayerAudience(uuid, null, null, null, false, this); Player player = server.getPlayer(username);
} return player != null ? player : username;
@Override
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
Collection<? extends Player> players = server.getOnlinePlayers();
Collection<String> 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;
} }
@Override @Override
public boolean hasPermission(Object player, String permission) { public boolean hasPermission(Object player, String permission) {
return cast(player).hasPermission(permission); return ((CommandSender) player).hasPermission(permission);
}
@Override
public Collection<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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));
} }
@Override @Override
@@ -163,10 +117,11 @@ public final class SpigotCommandUtil implements CommandUtil {
} }
@Override @Override
public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { public void kickPlayer(Object player, String message) {
// Have to run this in the main thread so we don't get a `Asynchronous player kick!` error // can also be console
Bukkit.getScheduler().runTask(plugin, if (player instanceof Player) {
() -> cast(player).kickPlayer(translateAndTransform(locale, message, args))); Bukkit.getScheduler().runTask(plugin, () -> ((Player) player).kickPlayer(message));
}
} }
@Override @Override
@@ -178,18 +133,4 @@ public final class SpigotCommandUtil implements CommandUtil {
public boolean removePlayerFromWhitelist(UUID uuid, String username) { public boolean removePlayerFromWhitelist(UUID uuid, String username) {
return WhitelistUtils.removePlayer(uuid, 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;
}
}
} }

View File

@@ -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));
}
}

View File

@@ -23,23 +23,27 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.player; package org.geysermc.floodgate.util;
import java.util.UUID; import org.bukkit.Bukkit;
import net.kyori.adventure.audience.Audience; import org.geysermc.floodgate.platform.util.PlatformUtils;
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;
public interface ServerAudience extends Audience { public class SpigotPlatformUtils extends PlatformUtils {
@NonNull Iterable<? extends UserAudience> onlineAudiences(); @Override
public AuthType authType() {
@NonNull ConsoleAudience consoleAudience(); if (Bukkit.getOnlineMode()) {
return AuthType.ONLINE;
@NonNegative int onlineCount(); }
return ProxyUtils.isProxyData() ? AuthType.PROXIED : AuthType.OFFLINE;
@Nullable UserAudience audienceOf(final @NonNull UUID uuid); }
@Nullable UserAudience audienceOf(final @NonNull String username); @Override
public String minecraftVersion() {
return Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
}
@Override
public String serverImplementationName() {
return Bukkit.getServer().getName();
}
} }

View File

@@ -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;
}
}
}

View File

@@ -5,7 +5,7 @@ var guavaVersion = "25.1-jre"
dependencies { dependencies {
api(projects.core) api(projects.core)
api("cloud.commandframework", "cloud-velocity", Versions.cloudVersion) implementation("cloud.commandframework", "cloud-velocity", Versions.cloudVersion)
} }
relocate("cloud.commandframework") relocate("cloud.commandframework")
@@ -14,7 +14,6 @@ relocate("io.leangen.geantyref")
// these dependencies are already present on the platform // 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.code.gson", "gson", gsonVersion)
provided("com.google.guava", "guava", guavaVersion) provided("com.google.guava", "guava", guavaVersion)
provided("com.google.inject", "guice", Versions.guiceVersion) provided("com.google.inject", "guice", Versions.guiceVersion)

View File

@@ -82,7 +82,7 @@ public final class VelocityInjector extends CommonPlatformInjector {
@Override @Override
public boolean removeInjection() { 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; return false;
} }

View File

@@ -44,18 +44,19 @@ import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.GameProfile.Property;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.Collections;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.VelocityCommandUtil;
public final class VelocityListener { public final class VelocityListener {
private static final Field INITIAL_MINECRAFT_CONNECTION; private static final Field INITIAL_MINECRAFT_CONNECTION;
@@ -89,6 +90,7 @@ public final class VelocityListener {
.expireAfterAccess(20, TimeUnit.SECONDS) .expireAfterAccess(20, TimeUnit.SECONDS)
.build(); .build();
@Inject private ProxyFloodgateConfig config;
@Inject private ProxyFloodgateApi api; @Inject private ProxyFloodgateApi api;
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@@ -141,8 +143,17 @@ public final class VelocityListener {
FloodgatePlayer player = playerCache.getIfPresent(event.getConnection()); FloodgatePlayer player = playerCache.getIfPresent(event.getConnection());
if (player != null) { if (player != null) {
playerCache.invalidate(event.getConnection()); 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) @Subscribe(order = PostOrder.LAST)
public void onDisconnect(DisconnectEvent event) { public void onDisconnect(DisconnectEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId()); api.playerRemoved(event.getPlayer().getUniqueId());
VelocityCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo
} }
} }

View File

@@ -48,6 +48,7 @@ import org.geysermc.floodgate.logger.Slf4jFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; 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.FloodgateCommandPreprocessor;
import org.geysermc.floodgate.player.UserAudience; import org.geysermc.floodgate.player.UserAudience;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager; 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.skin.SkinApplier;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.VelocityCommandUtil; import org.geysermc.floodgate.util.VelocityCommandUtil;
import org.geysermc.floodgate.util.VelocityPlatformUtils;
import org.geysermc.floodgate.util.VelocitySkinApplier; import org.geysermc.floodgate.util.VelocitySkinApplier;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -66,25 +68,25 @@ public final class VelocityPlatformModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
VelocityCommandUtil commandUtil = new VelocityCommandUtil();
requestInjection(commandUtil);
bind(CommandUtil.class).to(VelocityCommandUtil.class); bind(CommandUtil.class).to(VelocityCommandUtil.class);
bind(VelocityCommandUtil.class).toInstance(commandUtil); bind(PlatformUtils.class).to(VelocityPlatformUtils.class);
}
@Provides
@Singleton
public CommandManager<UserAudience> commandManager(CommandUtil commandUtil) {
Injector child = guice.createChildInjector(new CloudInjectionModule<>( Injector child = guice.createChildInjector(new CloudInjectionModule<>(
UserAudience.class, UserAudience.class,
CommandExecutionCoordinator.simpleCoordinator(), CommandExecutionCoordinator.simpleCoordinator(),
commandUtil::getAudience, commandUtil::getUserAudience,
audience -> (CommandSource) audience.source() audience -> (CommandSource) audience.source()
)); ));
CommandManager<UserAudience> commandManager = CommandManager<UserAudience> commandManager =
child.getInstance(new Key<VelocityCommandManager<UserAudience>>() {}); child.getInstance(new Key<VelocityCommandManager<UserAudience>>() {});
bind(new Key<CommandManager<UserAudience>>() {}).toInstance(commandManager);
commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil));
return commandManager;
} }
@Provides @Provides
@@ -99,7 +101,8 @@ public final class VelocityPlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public ListenerRegistration<Object> listenerRegistration(EventManager eventManager, public ListenerRegistration<Object> listenerRegistration(
EventManager eventManager,
VelocityPlugin plugin) { VelocityPlugin plugin) {
return new VelocityListenerRegistration(eventManager, plugin); return new VelocityListenerRegistration(eventManager, plugin);
} }

View File

@@ -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;
}
}
}

View File

@@ -29,35 +29,29 @@ import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.Optional;
import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; 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.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandUtil; 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.UserAudience;
import org.geysermc.floodgate.player.UserAudienceArgument.PlayerType; import org.geysermc.floodgate.player.UserAudience.ConsoleAudience;
import org.geysermc.floodgate.player.VelocityUserAudience.VelocityConsoleAudience; import org.geysermc.floodgate.player.UserAudience.PlayerAudience;
import org.geysermc.floodgate.player.VelocityUserAudience.VelocityPlayerAudience;
public final class VelocityCommandUtil implements CommandUtil { public final class VelocityCommandUtil extends CommandUtil {
public static final @NonNull Map<UUID, UserAudience> AUDIENCE_CACHE = new HashMap<>();
private static UserAudience console; private static UserAudience console;
@Inject private ProxyServer server; @Inject private ProxyServer server;
@Inject private FloodgateApi api;
@Inject private FloodgateLogger logger; @Inject
@Inject private LanguageManager manager; public VelocityCommandUtil(LanguageManager manager, FloodgateApi api) {
super(manager, api);
}
@Override @Override
public @NonNull UserAudience getAudience(@NonNull Object sourceObj) { public @NonNull UserAudience getUserAudience(@NonNull Object sourceObj) {
if (!(sourceObj instanceof CommandSource)) { if (!(sourceObj instanceof CommandSource)) {
throw new IllegalArgumentException("Can only work with CommandSource!"); throw new IllegalArgumentException("Can only work with CommandSource!");
} }
@@ -67,7 +61,7 @@ public final class VelocityCommandUtil implements CommandUtil {
if (console != null) { if (console != null) {
return console; return console;
} }
return console = new VelocityConsoleAudience(source, this); return console = new ConsoleAudience(source, this);
} }
Player player = (Player) source; Player player = (Player) source;
@@ -75,84 +69,39 @@ public final class VelocityCommandUtil implements CommandUtil {
String username = player.getUsername(); String username = player.getUsername();
String locale = Utils.getLocale(player.getPlayerSettings().getLocale()); String locale = Utils.getLocale(player.getPlayerSettings().getLocale());
return AUDIENCE_CACHE.computeIfAbsent(uuid, return new PlayerAudience(uuid, username, locale, source, this, true);
$ -> new VelocityPlayerAudience(uuid, username, locale, source, true, this));
} }
@Override @Override
public @Nullable UserAudience getAudienceByUsername(@NonNull String username) { protected String getUsernameFromSource(@NonNull Object source) {
return server.getPlayer(username) return ((Player) source).getUsername();
.map(this::getAudience)
.orElse(null);
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUsername(@NonNull String username) { protected UUID getUuidFromSource(@NonNull Object source) {
return new VelocityPlayerAudience(null, username, null, null, false, this); return ((Player) source).getUniqueId();
} }
@Override @Override
public @Nullable UserAudience getAudienceByUuid(@NonNull UUID uuid) { protected Collection<?> getOnlinePlayers() {
return server.getPlayer(uuid) return server.getAllPlayers();
.map(this::getAudience)
.orElse(null);
} }
@Override @Override
public @NonNull UserAudience getOfflineAudienceByUuid(@NonNull UUID uuid) { public Object getPlayerByUuid(@NonNull UUID uuid) {
return new VelocityPlayerAudience(uuid, null, null, null, false, this); Optional<Player> player = server.getPlayer(uuid);
return player.isPresent() ? player.get() : uuid;
} }
@Override @Override
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) { public Object getPlayerByUsername(@NonNull String username) {
Collection<Player> players = server.getAllPlayers(); Optional<Player> player = server.getPlayer(username);
return player.isPresent() ? player.get() : username;
Collection<String> 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;
} }
@Override @Override
public boolean hasPermission(Object player, String permission) { public boolean hasPermission(Object player, String permission) {
return cast(player).hasPermission(permission); return ((CommandSource) player).hasPermission(permission);
}
@Override
public Collection<Object> getOnlinePlayersWithPermission(String permission) {
List<Object> 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));
} }
@Override @Override
@@ -161,23 +110,9 @@ public final class VelocityCommandUtil implements CommandUtil {
} }
@Override @Override
public void kickPlayer(Object player, String locale, TranslatableMessage message, Object... args) { public void kickPlayer(Object player, String message) {
cast(player).disconnect(translateAndTransform(locale, message, args)); if (player instanceof Player) {
} ((Player) player).disconnect(Component.text(message));
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;
} }
} }
} }

View File

@@ -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();
}
}