diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 016ad98d..ca715239 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -17,7 +17,7 @@ jobs: java-version: '17' cache: 'gradle' - - name: Build with Maven + - name: Build with Gradle run: ./gradlew build - name: Archive artifacts (Floodgate Bungee) @@ -27,6 +27,13 @@ jobs: name: Floodgate Bungee path: bungee/build/libs/floodgate-bungee.jar + - name: Archive artifacts (Floodgate Fabric) + uses: actions/upload-artifact@v2 + if: success() + with: + name: Floodgate Fabric + path: fabric/build/libs/floodgate-fabric.jar + - name: Archive artifacts (Floodgate Spigot) uses: actions/upload-artifact@v2 if: success() diff --git a/build.gradle.kts b/build.gradle.kts index eacb79f5..c44f0e61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,7 @@ allprojects { } //todo differentiate maven publishing from downloads publishing +//todo add fabric projects to be deployed val deployProjects = setOf( projects.api, // for future Floodgate integration + Fabric @@ -36,7 +37,6 @@ val deployProjects = setOf( val shadowProjects = setOf( projects.api, - // for future Floodgate integration + Fabric projects.core, projects.bungeeBase, projects.spigotBase, @@ -44,6 +44,12 @@ val shadowProjects = setOf( projects.universal ).map { it.dependencyProject } +val moddedProjects = setOf( + projects.mod.commonBase, + projects.fabric, + projects.mod.fabricBase +).map { it.dependencyProject } + //todo re-add checkstyle when we switch back to 2 space indention // and take a look again at spotbugs someday @@ -56,6 +62,7 @@ subprojects { when (this) { in deployProjects -> plugins.apply("floodgate.publish-conventions") + in moddedProjects -> plugins.apply("floodgate.modded-conventions") else -> plugins.apply("floodgate.base-conventions") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index fbde75bd..7c282367 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,6 +4,11 @@ plugins { repositories { gradlePluginPortal() + + maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://maven.fabricmc.net/") + maven("https://maven.minecraftforge.net/") + maven("https://maven.architectury.dev/") } dependencies { @@ -11,4 +16,9 @@ dependencies { implementation(libs.indra.git) implementation(libs.shadow) implementation(libs.gradle.idea.ext) + implementation(libs.architectury.plugin) + implementation(libs.architectury.loom) + + // TODO: Add modrinth + //implementation("com.modrinth.minotaur:Minotaur:2.7.5") } diff --git a/buildSrc/src/main/kotlin/floodgate.base-conventions.gradle.kts b/buildSrc/src/main/kotlin/floodgate.base-conventions.gradle.kts index d2a608a7..fd63bede 100644 --- a/buildSrc/src/main/kotlin/floodgate.base-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/floodgate.base-conventions.gradle.kts @@ -19,12 +19,13 @@ indra { javaVersions { target(17) + strictVersions(true) } } tasks { processResources { - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json")) { + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json")) { expand( "id" to "floodgate", "name" to "floodgate", diff --git a/buildSrc/src/main/kotlin/floodgate.modded-conventions.gradle.kts b/buildSrc/src/main/kotlin/floodgate.modded-conventions.gradle.kts new file mode 100644 index 00000000..7fa9882d --- /dev/null +++ b/buildSrc/src/main/kotlin/floodgate.modded-conventions.gradle.kts @@ -0,0 +1,30 @@ +plugins { + id("floodgate.base-conventions") + id("architectury-plugin") apply true + id("dev.architectury.loom") apply true +} + +architectury { + minecraft = "1.20.2" +} + +loom { + silentMojangMappingsLicense() + mixin.defaultRefmapName.set("floodgate-mod-refmap.json") +} + +dependencies { + minecraft("com.mojang:minecraft:1.20.2") + mappings(loom.officialMojangMappings()) +} + +repositories { + maven("https://repo.opencollab.dev/maven-releases/") + maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://jitpack.io") + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://maven.fabricmc.net/") + maven("https://maven.minecraftforge.net/") + maven("https://maven.architectury.dev/") +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 632ea0bc..9ea47858 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { annotationProcessor(libs.micronaut.data.processor) compileOnlyApi(libs.jakarta.persistence) - runtimeOnly("com.h2database:h2") + //runtimeOnly("com.h2database:h2") testImplementation(libs.junit.jupiter) testRuntimeOnly(libs.junit.platform.launcher) diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/NewsChecker.java b/core/src/main/java/org/geysermc/floodgate/core/news/NewsChecker.java index ea80262d..a699ca01 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/NewsChecker.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/NewsChecker.java @@ -40,15 +40,13 @@ import java.util.concurrent.TimeUnit; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.command.util.Permission; +import org.geysermc.floodgate.core.news.data.AnnouncementData; +import org.geysermc.floodgate.core.news.data.BuildSpecificData; +import org.geysermc.floodgate.core.news.data.CheckAfterData; import org.geysermc.floodgate.core.platform.command.CommandUtil; import org.geysermc.floodgate.core.util.Constants; import org.geysermc.floodgate.core.util.HttpClient; import org.geysermc.floodgate.core.util.HttpClient.HttpResponse; -import org.geysermc.floodgate.news.NewsItem; -import org.geysermc.floodgate.news.NewsItemAction; -import org.geysermc.floodgate.news.data.AnnouncementData; -import org.geysermc.floodgate.news.data.BuildSpecificData; -import org.geysermc.floodgate.news.data.CheckAfterData; @Singleton public class NewsChecker { diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItem.java b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItem.java index 9a7b20a3..29431751 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItem.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItem.java @@ -23,14 +23,15 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.news; +package org.geysermc.floodgate.core.news; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.geysermc.floodgate.news.data.ItemData; + +import org.geysermc.floodgate.core.news.data.ItemData; public final class NewsItem { private final int id; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemAction.java b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemAction.java index 00e34b62..6f654409 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemAction.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemAction.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.news; +package org.geysermc.floodgate.core.news; public enum NewsItemAction { ON_SERVER_STARTED, diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemMessage.java b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemMessage.java index 9c2f3d15..b8561b9f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemMessage.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/NewsItemMessage.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.news; +package org.geysermc.floodgate.core.news; import com.google.gson.JsonArray; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/NewsType.java b/core/src/main/java/org/geysermc/floodgate/core/news/NewsType.java index 95f1f1ad..b8fde660 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/NewsType.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/NewsType.java @@ -23,15 +23,12 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.news; +package org.geysermc.floodgate.core.news; import com.google.gson.JsonObject; import java.util.function.Function; -import org.geysermc.floodgate.news.data.AnnouncementData; -import org.geysermc.floodgate.news.data.BuildSpecificData; -import org.geysermc.floodgate.news.data.CheckAfterData; -import org.geysermc.floodgate.news.data.ConfigSpecificData; -import org.geysermc.floodgate.news.data.ItemData; + +import org.geysermc.floodgate.core.news.data.*; public enum NewsType { BUILD_SPECIFIC(BuildSpecificData::read), diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/data/AnnouncementData.java b/core/src/main/java/org/geysermc/floodgate/core/news/data/AnnouncementData.java index 71aae853..e439944b 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/data/AnnouncementData.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/data/AnnouncementData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.news.data; +package org.geysermc.floodgate.core.news.data; import com.google.gson.JsonArray; import com.google.gson.JsonElement; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/data/BuildSpecificData.java b/core/src/main/java/org/geysermc/floodgate/core/news/data/BuildSpecificData.java index 7f2c7360..fb61ba90 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/data/BuildSpecificData.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/data/BuildSpecificData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.news.data; +package org.geysermc.floodgate.core.news.data; import com.google.gson.JsonObject; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/data/CheckAfterData.java b/core/src/main/java/org/geysermc/floodgate/core/news/data/CheckAfterData.java index 1e5fef4d..eca89227 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/data/CheckAfterData.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/data/CheckAfterData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.news.data; +package org.geysermc.floodgate.core.news.data; import com.google.gson.JsonObject; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/data/ConfigSpecificData.java b/core/src/main/java/org/geysermc/floodgate/core/news/data/ConfigSpecificData.java index 88b5897a..0e2278b4 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/data/ConfigSpecificData.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/data/ConfigSpecificData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.news.data; +package org.geysermc.floodgate.core.news.data; import com.google.gson.JsonArray; import com.google.gson.JsonElement; diff --git a/core/src/main/java/org/geysermc/floodgate/core/news/data/ItemData.java b/core/src/main/java/org/geysermc/floodgate/core/news/data/ItemData.java index 64a1aedf..30d0b30f 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/news/data/ItemData.java +++ b/core/src/main/java/org/geysermc/floodgate/core/news/data/ItemData.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.news.data; +package org.geysermc.floodgate.core.news.data; public interface ItemData { } diff --git a/core/src/main/resources/languages b/core/src/main/resources/languages index 204f4fe4..df599a9b 160000 --- a/core/src/main/resources/languages +++ b/core/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 204f4fe4920defac3a472e762d95233d0756f35f +Subproject commit df599a9bc9d7fce93a586591d6c0d625eefe4463 diff --git a/gradle.properties b/gradle.properties index d355fc77..44b295b2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,7 @@ org.gradle.parallel=true systemProp.org.gradle.unsafe.kotlin.assignment=true version=2.2.2-SNAPSHOT -micronautVersion=4.1.3 \ No newline at end of file +micronautVersion=4.1.3 + +# Increase ram for Gradle JVM +org.gradle.jvmargs=-Xmx4G diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b71e68a0..5dadb460 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,9 +14,10 @@ base-api = "feature-floodgate-merge-1.1.0-SNAPSHOT" config-utils = "development-2.0-SNAPSHOT" fastutil = "8.5.3" java-websocket = "1.5.2" -cloud = "1.5.0" +cloud = "1.8.3" snakeyaml = "2.0" bstats = "3.0.2" +jakarta-persistence = "3.1.0" # bungee bungee = "master-SNAPSHOT" @@ -28,6 +29,22 @@ authlib = "5.0.47" # velocity velocity = "3.2.0-SNAPSHOT" +# fabric +fabric_minecraft = "1.20.2" +fabric_loader = "0.14.20" +fabric_api = "0.90.7+1.20.2" +fabric_adventure = "5.4.0-SNAPSHOT" +fabric_permissions_api = "0.2-SNAPSHOT" + +# forge + +# mod common +mixin = "0.8.5" + +# architectury +architectury_plugin = "3.4-SNAPSHOT" +architectury_loom = "1.4-SNAPSHOT" + # buildSrc indra = "3.1.3" shadow = "8.1.1" @@ -64,7 +81,7 @@ micronaut-validation-processor = { module = "io.micronaut.validation:micronaut-v micronaut-data-processor = { module = "io.micronaut.data:micronaut-data-processor" } micronaut-data-jdbc = { module = "io.micronaut.data:micronaut-data-jdbc" } micronaut-hikari = { module = "io.micronaut.sql:micronaut-jdbc-hikari" } -jakarta-persistence = { module = "jakarta.persistence:jakarta.persistence-api" } +jakarta-persistence = { module = "jakarta.persistence:jakarta.persistence-api", version.ref = "jakarta-persistence" } micronaut-serde-jsonp = { module = "io.micronaut.serde:micronaut-serde-jsonp" } micronaut-serde-processor = { module = "io.micronaut.serde:micronaut-serde-processor" } @@ -91,13 +108,30 @@ authlib = { module = "com.mojang:authlib", version.ref = "authlib" } cloud-velocity = { module = "cloud.commandframework:cloud-velocity", version.ref = "cloud" } velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } +# fabric +# check these on https://modmuss50.me/fabric.html +fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric_minecraft" } +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric_loader" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric_api" } +cloud-fabric = { module = "cloud.commandframework:cloud-fabric", version.ref = "cloud" } +kyori-adventure = { module = "net.kyori:adventure-platform-fabric", version.ref = "fabric_adventure" } +fabric-permissions-api = { module = "me.lucko:fabric-permissions-api", version.ref = "fabric_permissions_api" } + +# forge TODO + +# mod common +mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } + # buildSrc checker-qual = { module = "org.checkerframework:checker-qual", version.ref = "checkerframework" } + # plugins indra-common = { module = "net.kyori:indra-common", version.ref = "indra" } indra-git = { module = "net.kyori:indra-git", version.ref = "indra" } shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadow" } gradle-idea-ext = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "gradle-idea-ext" } +architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury.loom.gradle.plugin", version.ref = "architectury_loom" } +architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury_plugin" } [plugins] micronaut = { id = "io.micronaut.library", version.ref = "micronaut-gradle" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79..033e24c4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17a8ddce..3fa8f862 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb..fcb6fca1 100755 --- a/gradlew +++ b/gradlew @@ -130,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/isolation/src/main/java/org/geysermc/floodgate/isolation/loader/PlatformLoader.java b/isolation/src/main/java/org/geysermc/floodgate/isolation/loader/PlatformLoader.java index 133930c6..bab1a413 100644 --- a/isolation/src/main/java/org/geysermc/floodgate/isolation/loader/PlatformLoader.java +++ b/isolation/src/main/java/org/geysermc/floodgate/isolation/loader/PlatformLoader.java @@ -32,6 +32,8 @@ import org.geysermc.floodgate.isolation.util.UrlUtil; public class PlatformLoader { + + //TODO: Mixins need to be loaded *before* we try to enable/download Floodgate... public static LibraryManager createLibraryManager(ClassLoader loader, Path cacheDirectory) { return new LibraryManager(loader, cacheDirectory, true) .addLibrary( diff --git a/mod/common/base/build.gradle.kts b/mod/common/base/build.gradle.kts new file mode 100644 index 00000000..a4eb63bf --- /dev/null +++ b/mod/common/base/build.gradle.kts @@ -0,0 +1,18 @@ +architectury { + common("fabric") +} + +loom { + accessWidenerPath.set(file("src/main/resources/floodgate.accesswidener")) +} + +dependencies { + api(projects.core) + + compileOnly(libs.mixin) + annotationProcessor(projects.core) + annotationProcessor(libs.micronaut.inject.java) + compileOnlyApi(projects.isolation) +} + +provided(libs.gson) \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/ModPlatform.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/ModPlatform.java new file mode 100644 index 00000000..a02b2cc2 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/ModPlatform.java @@ -0,0 +1,26 @@ +package org.geysermc.floodgate.mod; + +import io.micronaut.context.ApplicationContext; +import lombok.Getter; +import org.geysermc.floodgate.core.FloodgatePlatform; +import org.geysermc.floodgate.isolation.library.LibraryManager; + +public abstract class ModPlatform extends FloodgatePlatform { + + @Getter + protected ApplicationContext context; + + protected ModPlatform(LibraryManager manager) { + super(manager); + } + + @Override + public void enable() throws RuntimeException { + super.enable(); + } + + @Override + public boolean isProxy() { + return false; + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataAddon.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataAddon.java new file mode 100644 index 00000000..41473fff --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataAddon.java @@ -0,0 +1,64 @@ +package org.geysermc.floodgate.mod.addon.data; + +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.connection.DataSeeker; +import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.core.util.Utils; + +@Singleton +public class ModDataAddon implements InjectorAddon { + + @Inject + DataSeeker dataSeeker; + + @Inject + FloodgateDataHandler handshakeHandler; + + @Inject + FloodgateConfig config; + + @Inject + FloodgateLogger logger; + + @Inject + @Named("packetHandler") + String packetHandlerName; + + @Inject + @Named("connectionAttribute") + AttributeKey connectionAttribute; + + @Inject + @Named("kickMessageAttribute") + AttributeKey kickMessageAttribute; + + @Override + public void onInject(Channel channel, boolean toServer) { + var dataHandler = new ModDataHandler( + dataSeeker, handshakeHandler, config, logger, connectionAttribute, kickMessageAttribute); + channel.pipeline().addBefore(packetHandlerName, "floodgate_data_handler", dataHandler); + } + + @Override + public void onChannelClosed(Channel channel) { + InjectorAddon.super.onChannelClosed(channel); + } + + @Override + public void onRemoveInject(Channel channel) { + Utils.removeHandler(channel.pipeline(), "floodgate_data_handler"); + } + + @Override + public boolean shouldInject() { + return false; + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataHandler.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataHandler.java new file mode 100644 index 00000000..e2f122f2 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/addon/data/ModDataHandler.java @@ -0,0 +1,164 @@ +package org.geysermc.floodgate.mod.addon.data; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AttributeKey; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import net.minecraft.DefaultUncaughtExceptionHandler; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; +import net.minecraft.network.protocol.login.ServerboundHelloPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.addon.data.CommonNettyDataHandler; +import org.geysermc.floodgate.core.addon.data.PacketBlocker; +import org.geysermc.floodgate.core.config.FloodgateConfig; +import org.geysermc.floodgate.core.connection.DataSeeker; +import org.geysermc.floodgate.core.connection.FloodgateDataHandler; +import org.geysermc.floodgate.mod.mixin.ClientIntentionPacketMixinInterface; +import org.geysermc.floodgate.mod.mixin.ConnectionMixin; +import org.slf4j.Logger; + +import java.net.InetSocketAddress; + +public class ModDataHandler extends CommonNettyDataHandler { + + @Inject + FloodgateLogger logger; + + private net.minecraft.network.Connection networkManager; + + @Inject + @Named("minecraftServer") + MinecraftServer minecraftServer; + + private Connection player; + public ModDataHandler( + DataSeeker dataSeeker, + FloodgateDataHandler handshakeHandler, + FloodgateConfig config, + FloodgateLogger logger, + AttributeKey connectionAttribute, + AttributeKey kickMessageAttribute) { + super( + dataSeeker, + handshakeHandler, + config, + logger, + connectionAttribute, + kickMessageAttribute, + new PacketBlocker()); + } + + @Override + protected void setNewIp(Channel channel, InetSocketAddress newIp) { + ((ConnectionMixin) this.networkManager).setAddress(newIp); + } + + @Override + protected Object setHostname(Object handshakePacket, String hostname) { + // While it would be ideal to simply create a new handshake packet, the packet constructor + // does not allow us to set the protocol version + ((ClientIntentionPacketMixinInterface) handshakePacket).setAddress(hostname); + return handshakePacket; + } + + @Override + protected boolean shouldRemoveHandler(FloodgateDataHandler.HandleResult result) { + player = result.joinResult().connection(); + + if (getKickMessage() != null) { + // we also have to keep this handler if we want to kick then with a disconnect message + return false; + } else if (player == null) { + // player is not a Floodgate player + return true; + } + + if (!result.joinResult().shouldDisconnect()) { + logger.info("Floodgate player who is logged in as {} {} joined", + player.javaUsername(), player.javaUuid()); + } + + // Handler will be removed after the login hello packet is handled + return false; + } + + @Override + protected boolean channelRead(Object packet) throws Exception { + if (packet instanceof ClientIntentionPacket intentionPacket) { + ctx.pipeline().addAfter("splitter", "floodgate_packet_blocker", blocker); + networkManager = (net.minecraft.network.Connection) ctx.channel().pipeline().get("packet_handler"); + handle(packet, intentionPacket.hostName()); + return false; + } + return !checkAndHandleLogin(packet); + } + + private boolean checkAndHandleLogin(Object packet) { + if (packet instanceof ServerboundHelloPacket) { + String kickMessage = getKickMessage(); + if (kickMessage != null) { + networkManager.disconnect(Component.nullToEmpty(kickMessage)); + return true; + } + + // we have to fake the offline player (login) cycle + if (!(networkManager.getPacketListener() instanceof ServerLoginPacketListenerImpl packetListener)) { + // player is not in the login state, abort + ctx.pipeline().remove(this); + return true; + } + + GameProfile gameProfile = new GameProfile(player.javaUuid(), player.javaUsername()); + + if (player.isLinked() && player.javaUuid().version() == 4) { + verifyLinkedPlayerAsync(packetListener, gameProfile); + } else { + packetListener.startClientVerification(gameProfile); + } + + ctx.pipeline().remove(this); + return true; + } + return false; + } + + /** + * Starts a new thread that fetches the linked player's textures, + * and then starts client verification with the more accurate game profile. + * + * @param packetListener the login packet listener for this connection + * @param gameProfile the player's initial profile. it will NOT be mutated. + */ + private void verifyLinkedPlayerAsync(ServerLoginPacketListenerImpl packetListener, GameProfile gameProfile) { + Thread texturesThread = new Thread("Bedrock Linked Player Texture Download") { + @Override + public void run() { + GameProfile effectiveProfile = gameProfile; + try { + MinecraftSessionService service = minecraftServer.getSessionService(); + effectiveProfile = service.fetchProfile(effectiveProfile.getId(), true).profile(); + } catch (Exception e) { + logger.error("Unable to get Bedrock linked player textures for " + effectiveProfile.getName(), e); + } + packetListener.startClientVerification(effectiveProfile); + } + }; + texturesThread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler((Logger) logger)); + texturesThread.start(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + if (config.debug()) { + cause.printStackTrace(); + } + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java new file mode 100644 index 00000000..f69b8dbb --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/inject/ModInjector.java @@ -0,0 +1,65 @@ +package org.geysermc.floodgate.mod.inject; + +import io.netty.channel.*; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.core.inject.CommonPlatformInjector; + +@Singleton +public class ModInjector extends CommonPlatformInjector { + + @Inject FloodgateLogger logger; + + private static ModInjector instance; + + @Override + public void inject() throws Exception { + // handled by Mixin + } + public void injectClient(ChannelFuture future) { + future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) throws Exception { + super.channelRead(ctx, msg); + + Channel channel = (Channel) msg; + channel.pipeline().addLast(new ChannelInitializer() { + @Override + protected void initChannel(@NonNull Channel channel) { + injectAddonsCall(channel, false); + addInjectedClient(channel); + channel.closeFuture().addListener(listener -> { + channelClosedCall(channel); + removeInjectedClient(channel); + }); + } + }); + } + }); + } + + @Override + public boolean canRemoveInjection() { + return false; + } + + @Override + public void removeInjection() throws Exception { + // not needed + } + + @Override + public boolean isInjected() { + return true; // handled by Mixin + } + + public static ModInjector getInstance() { + return instance; + } + + public static void setInstance(ModInjector injector) { + instance = injector; + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java new file mode 100644 index 00000000..f36c81b5 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ChunkMapMixin.java @@ -0,0 +1,12 @@ +package org.geysermc.floodgate.mod.mixin; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minecraft.server.level.ChunkMap; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ChunkMap.class) +public interface ChunkMapMixin { + @Accessor("entityMap") + Int2ObjectMap getEntityMap(); +} \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java new file mode 100644 index 00000000..7a9ee5cd --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixin.java @@ -0,0 +1,15 @@ +package org.geysermc.floodgate.mod.mixin; + + +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +@Mixin(ClientIntentionPacket.class) +public class ClientIntentionPacketMixin { + @ModifyConstant(method = "(Lnet/minecraft/network/FriendlyByteBuf;)V", constant = @Constant(intValue = 255)) + private static int floodgate$setHandshakeLength(int defaultValue) { + return Short.MAX_VALUE; + } +} \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java new file mode 100644 index 00000000..50e64471 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ClientIntentionPacketMixinInterface.java @@ -0,0 +1,14 @@ +package org.geysermc.floodgate.mod.mixin; + +import net.minecraft.network.protocol.handshake.ClientIntentionPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientIntentionPacket.class) +public interface ClientIntentionPacketMixinInterface { + + @Accessor("hostName") + @Mutable + void setAddress(String address); +} \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java new file mode 100644 index 00000000..3287e89a --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ConnectionMixin.java @@ -0,0 +1,13 @@ +package org.geysermc.floodgate.mod.mixin; + +import net.minecraft.network.Connection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.net.SocketAddress; + +@Mixin(Connection.class) +public interface ConnectionMixin { + @Accessor("address") + void setAddress(SocketAddress address); +} \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java new file mode 100644 index 00000000..8a19f360 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/mixin/ServerConnectionListenerMixin.java @@ -0,0 +1,25 @@ +package org.geysermc.floodgate.mod.mixin; + +import io.netty.channel.ChannelFuture; +import net.minecraft.server.network.ServerConnectionListener; +import org.geysermc.floodgate.mod.inject.ModInjector; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.net.InetAddress; +import java.util.List; + +@Mixin(ServerConnectionListener.class) +public abstract class ServerConnectionListenerMixin { + + @Shadow @Final private List channels; + + @Inject(method = "startTcpServerListener", at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/List;add(Ljava/lang/Object;)Z")) + public void onChannelAdd(InetAddress address, int port, CallbackInfo ci) { + ModInjector.getInstance().injectClient(this.channels.get(this.channels.size() - 1)); + } +} \ No newline at end of file diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/player/ModConnectionManager.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/player/ModConnectionManager.java new file mode 100644 index 00000000..7c7846dc --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/player/ModConnectionManager.java @@ -0,0 +1,15 @@ +package org.geysermc.floodgate.mod.player; + +import net.minecraft.world.entity.player.Player; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.core.connection.ConnectionManager; + +public class ModConnectionManager extends ConnectionManager { + @Override + protected @Nullable Object platformIdentifierOrConnectionFor(Object input) { + if (input instanceof Player player) { + return connectionByUuid(player.getUUID()); + } + return null; + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageRegistration.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageRegistration.java new file mode 100644 index 00000000..6f8a491d --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageRegistration.java @@ -0,0 +1,9 @@ +package org.geysermc.floodgate.mod.pluginmessage; + +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; + +public abstract class ModPluginMessageRegistration implements PluginMessageRegistration { + @Override + public abstract void register(PluginMessageChannel channel); +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageUtil.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageUtil.java new file mode 100644 index 00000000..86f7664d --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModPluginMessageUtil.java @@ -0,0 +1,6 @@ +package org.geysermc.floodgate.mod.pluginmessage; + +import org.geysermc.floodgate.core.platform.pluginmessage.PluginMessageUtils; + +public abstract class ModPluginMessageUtil extends PluginMessageUtils { +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java new file mode 100644 index 00000000..f17a392f --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/pluginmessage/ModSkinApplier.java @@ -0,0 +1,65 @@ +package org.geysermc.floodgate.mod.pluginmessage; + +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; +import org.geysermc.floodgate.core.skin.SkinApplier; +import org.geysermc.floodgate.mod.mixin.ChunkMapMixin; + +import java.util.Collections; + +public class ModSkinApplier implements SkinApplier { + + @Inject + @Named("minecraftServer") + MinecraftServer minecraftServer; + + @Override + public void applySkin(@NonNull Connection connection, SkinApplyEvent.@NonNull SkinData skinData) { + minecraftServer.execute(() -> { + ServerPlayer bedrockPlayer = minecraftServer.getPlayerList() + .getPlayer(connection.javaUuid()); + if (bedrockPlayer == null) { + // Disconnected probably? + return; + } + + // Apply the new skin internally + PropertyMap properties = bedrockPlayer.getGameProfile().getProperties(); + + properties.removeAll("textures"); + properties.put("textures", new Property("textures", skinData.value(), skinData.signature())); + + ChunkMap tracker = ((ServerLevel) bedrockPlayer.level).getChunkSource().chunkMap; + ChunkMap.TrackedEntity entry = ((ChunkMapMixin) tracker).getEntityMap().get(bedrockPlayer.getId()); + // Skin is applied - now it's time to refresh the player for everyone. + for (ServerPlayer otherPlayer : minecraftServer.getPlayerList().getPlayers()) { + boolean samePlayer = otherPlayer == bedrockPlayer; + if (!samePlayer) { + // TrackedEntity#broadcastRemoved doesn't actually remove them from seenBy + entry.removePlayer(otherPlayer); + } + + otherPlayer.connection.send(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(bedrockPlayer.getUUID()))); + otherPlayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(Collections.singletonList(bedrockPlayer))); + if (samePlayer) { + continue; + } + + if (bedrockPlayer.level == otherPlayer.level) { + entry.updatePlayer(otherPlayer); + } + } + }); + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java new file mode 100644 index 00000000..1f83c4c0 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModCommandUtil.java @@ -0,0 +1,131 @@ +package org.geysermc.floodgate.mod.util; + +import com.mojang.authlib.GameProfile; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.UserWhiteListEntry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.api.GeyserApiBase; +import org.geysermc.floodgate.core.connection.audience.UserAudience; +import org.geysermc.floodgate.core.platform.command.CommandUtil; +import org.geysermc.floodgate.core.util.LanguageManager; + +import java.util.Collection; +import java.util.UUID; + +@Singleton +public abstract class ModCommandUtil extends CommandUtil { + + private final MinecraftServer server; + + private final LanguageManager manager; + + private UserAudience.ConsoleAudience console; + + @Inject + public ModCommandUtil( + LanguageManager manager, + MinecraftServer server, + GeyserApiBase api + ) { + super(manager, api); + this.server = server; + this.manager = manager; + } + @Override + public @NonNull UserAudience getUserAudience(@NonNull Object source) { + if (!(source instanceof CommandSourceStack)) { + throw new IllegalArgumentException("Source has to be a CommandSourceStack!"); + } + CommandSourceStack sourceStack = (CommandSourceStack) source; + + if (!sourceStack.isPlayer()) { + if (console != null) { + return console; + } + return console = new UserAudience.ConsoleAudience(sourceStack, this); + } + + ServerPlayer player = sourceStack.getPlayer(); + assert player != null; + UUID uuid = player.getUUID(); + String username = player.getGameProfile().getName(); + String locale; + locale = player.clientInformation().language(); + + return new UserAudience.PlayerAudience(uuid, username, locale, sourceStack, this, true); + } + + @Override + protected String getUsernameFromSource(@NonNull Object source) { + return ((CommandSourceStack) source).getTextName(); + } + + @Override + protected UUID getUuidFromSource(@NonNull Object source) { + return ((ServerPlayer) source).getUUID(); + } + + @Override + protected Collection getOnlinePlayers() { + return server.getPlayerList().getPlayers(); + } + + @Override + public Object getPlayerByUuid(@NonNull UUID uuid) { + try { + return server.getPlayerList().getPlayer(uuid); + } catch (Exception e) { + return uuid; + } + } + + @Override + public Object getPlayerByUsername(@NonNull String username) { + ServerPlayer player = server.getPlayerList().getPlayerByName(username); + return player != null ? player : username; + } + + @Override + public abstract boolean hasPermission(Object player, String permission); + + @Override + public void sendMessage(Object target, String message) { + if (target instanceof ServerPlayer serverPlayer) { + serverPlayer.displayClientMessage(Component.literal(message), false); + } else { + ((CommandSourceStack) target).sendSystemMessage(Component.literal(message)); + } + } + + @Override + public void kickPlayer(Object player, String message) { + if (player instanceof ServerPlayer serverPlayer) { + serverPlayer.connection.disconnect(Component.literal(message)); + } + } + + @Override + public boolean whitelistPlayer(UUID uuid, String username) { + GameProfile profile = new GameProfile(uuid, username); + if (server.getPlayerList().isWhiteListed(profile)) { + return false; + } + server.getPlayerList().getWhiteList().add(new UserWhiteListEntry(profile)); + return true; + } + + @Override + public boolean removePlayerFromWhitelist(UUID uuid, String username) { + GameProfile profile = new GameProfile(uuid, username); + if (!server.getPlayerList().isWhiteListed(profile)) { + return false; + } + server.getPlayerList().getWhiteList().remove(profile); + return true; + } +} diff --git a/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java new file mode 100644 index 00000000..14649989 --- /dev/null +++ b/mod/common/base/src/main/java/org/geysermc/floodgate/mod/util/ModPlatformUtils.java @@ -0,0 +1,27 @@ +package org.geysermc.floodgate.mod.util; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.core.platform.util.PlatformUtils; + +public abstract class ModPlatformUtils extends PlatformUtils { + + @Inject + @Named("minecraftServer") + protected MinecraftServer server; + + @Override + public abstract AuthType authType(); + + @Override + public String minecraftVersion() { + return SharedConstants.getCurrentVersion().getName(); + } + + @Override + public String serverImplementationName() { + return server.getServerModName(); + } +} diff --git a/mod/common/base/src/main/resources/floodgate.accesswidener b/mod/common/base/src/main/resources/floodgate.accesswidener new file mode 100644 index 00000000..b2d6835b --- /dev/null +++ b/mod/common/base/src/main/resources/floodgate.accesswidener @@ -0,0 +1,8 @@ +accessWidener v1 named + +# For player skin refreshing +accessible class net/minecraft/server/level/ChunkMap$TrackedEntity +# To access skins +accessible field net/minecraft/world/entity/Entity level Lnet/minecraft/world/level/Level; +# For setting gameprofile and starting connection verification +accessible method net/minecraft/server/network/ServerLoginPacketListenerImpl startClientVerification (Lcom/mojang/authlib/GameProfile;)V \ No newline at end of file diff --git a/mod/fabric/base/build.gradle.kts b/mod/fabric/base/build.gradle.kts new file mode 100644 index 00000000..6b379b30 --- /dev/null +++ b/mod/fabric/base/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + application +} + +architectury { + platformSetupLoomIde() + fabric() +} + +loom { + accessWidenerPath.set(project(":mod:common-base").file("src/main/resources/floodgate.accesswidener")) +} + +dependencies { + api(projects.mod.commonBase) + + modApi(libs.fabric.api) + modImplementation(libs.fabric.loader) + + // Commands library implementation for Fabric + modImplementation(libs.cloud.fabric) { + because("Commands library implementation for Fabric") + } + + modImplementation(libs.kyori.adventure) { + because("Chat library implementation for Fabric that includes methods for communicating with the server") + // Thanks to zml for this fix + // The package modifies Brigadier which causes a LinkageError at runtime if included + exclude("ca.stellardrift", "colonel") + } +} + +// using loom requires us to re-define all repositories here, lol +repositories { + maven("https://repo.opencollab.dev/maven-releases/") + maven("https://repo.opencollab.dev/maven-snapshots/") + maven("https://jitpack.io") + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + mavenCentral() +} \ No newline at end of file diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/FabricPlatform.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/FabricPlatform.java new file mode 100644 index 00000000..9b42fcc4 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/FabricPlatform.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2023 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.fabric; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.inject.qualifiers.Qualifiers; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; +import org.geysermc.floodgate.isolation.library.LibraryManager; +import org.geysermc.floodgate.mod.ModPlatform; + +import java.nio.file.Path; + +public final class FabricPlatform extends ModPlatform { + ModContainer modContainer; + + MinecraftServer minecraftServer; + + private FabricPlatform(LibraryManager manager) { + super(manager); + } + + @Override + protected void onContextCreated(ApplicationContext context) { + context.registerSingleton(modContainer) + .registerSingleton( + Path.class, + FabricLoader.getInstance().getConfigDir().resolve("Floodgate-Fabric"), + Qualifiers.byName("dataDirectory") + ) + .registerSingleton( + MinecraftServer.class, + minecraftServer, + Qualifiers.byName("minecraftServer") + ); + this.context = context; + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/command/FabricCommandManager.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/command/FabricCommandManager.java new file mode 100644 index 00000000..58a21a79 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/command/FabricCommandManager.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2023 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.fabric.command; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.execution.CommandExecutionCoordinator; +import cloud.commandframework.fabric.FabricServerCommandManager; +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.SneakyThrows; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.commands.CommandSourceStack; +import org.geysermc.floodgate.core.connection.audience.FloodgateCommandPreprocessor; +import org.geysermc.floodgate.core.connection.audience.UserAudience; +import org.geysermc.floodgate.core.platform.command.CommandUtil; + +@Factory +public final class FabricCommandManager { + @Bean + @SneakyThrows + @Singleton + public CommandManager commandManager(CommandUtil commandUtil) { + CommandManager commandManager = new FabricServerCommandManager<>( + CommandExecutionCoordinator.simpleCoordinator(), + commandUtil::getUserAudience, + audience -> (CommandSourceStack) audience.source() + ); + commandManager.registerCommandPreProcessor(new FloodgateCommandPreprocessor<>(commandUtil)); + return commandManager; + } + + @Inject + void registerPermissions() { + // TODO: permissions on fabric? + } + +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventListener.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventListener.java new file mode 100644 index 00000000..173cd672 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventListener.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2023 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.fabric.listener; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.geysermc.floodgate.core.connection.ConnectionManager; +import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.util.LanguageManager; + +@Singleton +public final class FabricEventListener implements McListener { + + @Inject ConnectionManager connectionManager; + @Inject private LanguageManager languageManager; + + public void onPlayerJoin(ServerGamePacketListenerImpl networkHandler, PacketSender packetSender, MinecraftServer server) { + var connection = connectionManager.findPendingConnection(networkHandler.player.getUUID()); + if (connection == null) { + return; + } + + languageManager.loadLocale(connection.languageCode()); + connectionManager.addAcceptedConnection(connection); + } + + public void onPlayerDisconnect(ServerGamePacketListenerImpl listener, MinecraftServer server) { + connectionManager.removeConnection(listener.player); + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventRegistration.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventRegistration.java new file mode 100644 index 00000000..1909464b --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/listener/FabricEventRegistration.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2023 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.fabric.listener; + +import jakarta.inject.Inject; +import lombok.RequiredArgsConstructor; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import org.geysermc.floodgate.core.platform.listener.ListenerRegistration; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class FabricEventRegistration implements ListenerRegistration { + @Override + public void register(FabricEventListener listener) { + ServerPlayConnectionEvents.JOIN.register(listener::onPlayerJoin); + ServerPlayConnectionEvents.DISCONNECT.register(listener::onPlayerDisconnect); + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/module/FabricPlatformModule.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/module/FabricPlatformModule.java new file mode 100644 index 00000000..5c86fdb2 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/module/FabricPlatformModule.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019-2023 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.fabric.module; + +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import jakarta.inject.Named; +import jakarta.inject.Singleton; + +@Factory +public final class FabricPlatformModule { + + @Bean + @Named("packetEncoder") + @Singleton + public String packetEncoder() { + return "encoder"; + } + + @Bean + @Named("packetDecoder") + @Singleton + public String packetDecoder() { + return "decoder"; + } + + @Bean + @Named("packetHandler") + @Singleton + public String packetHandler() { + return "packet_handler"; + } + + @Bean + @Named("implementationName") + @Singleton + public String implementationName() { + return "Fabric"; + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageRegistration.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageRegistration.java new file mode 100644 index 00000000..2a23d96d --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageRegistration.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2023 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.fabric.pluginmessage; + +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.resources.ResourceLocation; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageChannel; +import org.geysermc.floodgate.core.pluginmessage.PluginMessageRegistration; +import org.geysermc.floodgate.mod.pluginmessage.ModPluginMessageRegistration; + +public class FabricPluginMessageRegistration extends ModPluginMessageRegistration { + + @Override + public void register(PluginMessageChannel channel) { + ServerPlayNetworking.registerGlobalReceiver(new ResourceLocation(channel.getIdentifier()), + (server, player, handler, buf, responseSender) -> { + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + channel.handleServerCall(bytes, player.getUUID(), player.getGameProfile().getName()); + }); + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageUtils.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageUtils.java new file mode 100644 index 00000000..57d54ca3 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/pluginmessage/FabricPluginMessageUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2023 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.fabric.pluginmessage; + +import io.netty.buffer.Unpooled; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.geysermc.floodgate.mod.pluginmessage.ModPluginMessageUtil; + +import java.util.UUID; + +public class FabricPluginMessageUtils extends ModPluginMessageUtil { + + @Inject + @Named("minecraftServer") + MinecraftServer server; + + @Override + public boolean sendMessage(UUID uuid, String channel, byte[] data) { + try { + ServerPlayer player = server.getPlayerList().getPlayer(uuid); + ResourceLocation resource = new ResourceLocation(channel); // automatically splits over the : + FriendlyByteBuf dataBuffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); + ServerPlayNetworking.send(player, resource, dataBuffer); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricCommandUtil.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricCommandUtil.java new file mode 100644 index 00000000..ba100756 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricCommandUtil.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2023 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.fabric.util; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.MinecraftServer; +import org.geysermc.api.GeyserApiBase; +import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.mod.util.ModCommandUtil; + +public final class FabricCommandUtil extends ModCommandUtil { + + public FabricCommandUtil(LanguageManager manager, MinecraftServer server, GeyserApiBase api) { + super(manager, server, api); + } + + @Override + public boolean hasPermission(Object player, String permission) { + return Permissions.check((CommandSourceStack) player, permission); + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricPlatformUtils.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricPlatformUtils.java new file mode 100644 index 00000000..ba4e486e --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/FabricPlatformUtils.java @@ -0,0 +1,22 @@ +package org.geysermc.floodgate.fabric.util; + +import net.fabricmc.loader.api.FabricLoader; +import org.geysermc.floodgate.mod.util.ModPlatformUtils; + +public class FabricPlatformUtils extends ModPlatformUtils { + + @Override + public AuthType authType() { + if (server.usesAuthentication()) { + return AuthType.ONLINE; + } + return isProxied() ? AuthType.PROXIED : AuthType.OFFLINE; + } + + private boolean isProxied() { + return FabricLoader.getInstance().isModLoaded("fabricproxy-lite") || + FabricLoader.getInstance().isModLoaded("fabricproxy") || + FabricLoader.getInstance().isModLoaded("fabroxy") || + FabricLoader.getInstance().isModLoaded("fabricproxy-legacy"); + } +} diff --git a/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/MixinConfigPlugin.java b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/MixinConfigPlugin.java new file mode 100644 index 00000000..a25df4a0 --- /dev/null +++ b/mod/fabric/base/src/main/java/org/geysermc/floodgate/fabric/util/MixinConfigPlugin.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019-2023 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.fabric.util; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public class MixinConfigPlugin implements IMixinConfigPlugin { + + @Override + public void onLoad(String mixinPackage) { + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (mixinClassName.equals("org.geysermc.floodgate.mixin.ClientIntentionPacketMixin")) { + //returns true if fabricproxy-lite is present, therefore loading the mixin. If not present, the mixin will not be loaded. + return FabricLoader.getInstance().isModLoaded("fabricproxy-lite"); + } + return true; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } +} \ No newline at end of file diff --git a/mod/fabric/base/src/main/resources/floodgate.mixins.json b/mod/fabric/base/src/main/resources/floodgate.mixins.json new file mode 100644 index 00000000..3f2899d2 --- /dev/null +++ b/mod/fabric/base/src/main/resources/floodgate.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.floodgate.mod.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "ChunkMapMixin", + "ClientIntentionPacketMixin", + "ClientIntentionPacketMixinInterface", + "ConnectionMixin", + "ServerConnectionListenerMixin" + ], + "refmap": "floodgate-fabric-refmap.json", + "injectors": { + "defaultRequire": 1 + } +} diff --git a/mod/fabric/isolated/build.gradle.kts b/mod/fabric/isolated/build.gradle.kts new file mode 100644 index 00000000..1cc64a75 --- /dev/null +++ b/mod/fabric/isolated/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + application +} + +architectury { + fabric() +} + +dependencies { + api(projects.isolation) + modApi(libs.fabric.api) + modImplementation(libs.fabric.loader) +} + +tasks { + jar { + //dependsOn(":fabric-base:build", configurations.runtimeClasspath) + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + //from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) + + archiveBaseName = "floodgate-${project.name}" + archiveVersion = "" + archiveClassifier = "" + + val fabricBaseJar = project.projects.mod + .fabricBase.dependencyProject + .buildDir + .resolve("libs") + .resolve("floodgate-fabric-base.jar") + + from(fabricBaseJar.parentFile) { + include(fabricBaseJar.name) + rename("floodgate-fabric-base.jar", "platform-base.jar") + into("bundled/") + } + } + } \ No newline at end of file diff --git a/mod/fabric/isolated/src/main/java/org/geysermc/floodgate/fabric/IsolatedFabricMod.java b/mod/fabric/isolated/src/main/java/org/geysermc/floodgate/fabric/IsolatedFabricMod.java new file mode 100644 index 00000000..88f45963 --- /dev/null +++ b/mod/fabric/isolated/src/main/java/org/geysermc/floodgate/fabric/IsolatedFabricMod.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2023 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.fabric; + +import java.util.List; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import org.geysermc.floodgate.isolation.loader.PlatformHolder; +import org.geysermc.floodgate.isolation.loader.PlatformLoader; +import net.minecraft.server.MinecraftServer; + +public final class IsolatedFabricMod implements ModInitializer { + private PlatformHolder holder; + + @Override + public void onInitialize() { + try { + var libsDirectory = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); + holder = PlatformLoader.loadDefault(getClass().getClassLoader(), libsDirectory); + holder.init(List.of(ModInitializer.class), List.of(this)); + } catch (Exception exception) { + throw new RuntimeException("Failed to load Floodgate", exception); + } + + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + onEnable(); + }); + + ServerLifecycleEvents.SERVER_STOPPING.register(server -> { + onDisable(); + }); + } + + public void onEnable() { + holder.load(); + try { + holder.enable(); + } catch (Exception exception) { + onDisable(); + throw exception; + } + } + + public void onDisable() { + holder.disable(); + } +} diff --git a/mod/fabric/isolated/src/main/resources/fabric.mod.json b/mod/fabric/isolated/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..419a4a05 --- /dev/null +++ b/mod/fabric/isolated/src/main/resources/fabric.mod.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 1, + "id": "floodgate", + "version": "${version}", + "name": "${name}", + "description": "${description}", + "authors": [ + "${author}" + ], + "contact": { + "website": "${url}", + "repo": "https://github.com/GeyserMC/Floodgate" + }, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "org.geysermc.floodgate.fabric.IsolatedFabricMod" + ] + }, + "depends": { + "fabricloader": ">=0.14.6", + "fabric": "*", + "minecraft": ">=1.19" + } +} diff --git a/mod/fabric/isolated/src/main/resources/org.geysermc.mainClass b/mod/fabric/isolated/src/main/resources/org.geysermc.mainClass new file mode 100644 index 00000000..f94e82bf --- /dev/null +++ b/mod/fabric/isolated/src/main/resources/org.geysermc.mainClass @@ -0,0 +1 @@ +org.geysermc.floodgate.fabric.FabricPlatform \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3beb2ed3..1d8861d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,17 +2,10 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { - repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositories { - mavenLocal() // Geyser, Cumulus etc. - maven("https://repo.opencollab.dev/maven-releases") { - mavenContent { releasesOnly() } - } - maven("https://repo.opencollab.dev/maven-snapshots") { - mavenContent { snapshotsOnly() } - } + maven("https://repo.opencollab.dev/main") // Paper, Velocity // maven("https://repo.papermc.io/repository/maven-releases") { @@ -21,13 +14,14 @@ dependencyResolutionManagement { // maven("https://repo.papermc.io/repository/maven-snapshots") { // mavenContent { snapshotsOnly() } // } + maven("https://repo.papermc.io/repository/maven-public") // Spigot maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") { mavenContent { snapshotsOnly() } } - // BungeeCord + // Spigot, BungeeCord maven("https://oss.sonatype.org/content/repositories/snapshots") { mavenContent { snapshotsOnly() } } @@ -37,18 +31,31 @@ dependencyResolutionManagement { mavenContent { releasesOnly() } } - mavenCentral() + // Fabric + maven("https://maven.fabricmc.net") + + // Forge + maven("https://maven.minecraftforge.net/") maven("https://jitpack.io") { content { includeGroupByRegex("com\\.github\\..*") } } + mavenCentral() + mavenLocal() } } pluginManagement { repositories { gradlePluginPortal() + + // Fabric, loom specifically + maven("https://maven.fabricmc.net") + + // Forge + maven("https://maven.minecraftforge.net/") } + plugins { id("net.kyori.indra") id("net.kyori.indra.git") @@ -73,3 +80,16 @@ arrayOf("bungee", "spigot", "velocity").forEach { platform -> project(id).projectDir = file("$platform/$it") } } + +include(":mod:common-base") +project(":mod:common-base").projectDir = file("mod/common/base") + +arrayOf("fabric").forEach { platform -> + arrayOf("base", "isolated").forEach { + var id = ":mod:$platform-$it" + // isolated is the new default + if (id.endsWith("-isolated")) id = ":$platform" + include(id) + project(id).projectDir = file("mod/$platform/$it") + } +} diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java index abf0df5a..169291c8 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotCommandUtil.java @@ -119,6 +119,7 @@ public void sendMessage(Object target, String message) { } @Override + @SuppressWarnings("deprecation") // kickPlayer is deprecated in paper; not spigot public void kickPlayer(Object player, String message) { // can also be console if (player instanceof Player) { diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotVersionSpecificMethods.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotVersionSpecificMethods.java index 81efc6b6..af0ee28a 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotVersionSpecificMethods.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/SpigotVersionSpecificMethods.java @@ -62,6 +62,7 @@ public final class SpigotVersionSpecificMethods { @Inject JavaPlugin plugin; + @SuppressWarnings("deprecation") public String getLocale(Player player) { if (OLD_GET_LOCALE == null) { return player.getLocale(); diff --git a/universal/build.gradle.kts b/universal/build.gradle.kts index c4665b2c..a1b6e451 100644 --- a/universal/build.gradle.kts +++ b/universal/build.gradle.kts @@ -5,6 +5,8 @@ plugins { provided(libs.bungee) provided(libs.paper.api) provided(libs.velocity.api) +provided(libs.fabric.loader) +provided(libs.fabric.api) // todo use an isolated class loader in the future provided(libs.gson) diff --git a/universal/src/main/java/org/geysermc/floodgate/universal/platform/FloodgateFabric.java b/universal/src/main/java/org/geysermc/floodgate/universal/platform/FloodgateFabric.java new file mode 100644 index 00000000..48826cc7 --- /dev/null +++ b/universal/src/main/java/org/geysermc/floodgate/universal/platform/FloodgateFabric.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.universal.platform; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.loader.api.FabricLoader; +import org.geysermc.floodgate.universal.UniversalLoader; +import org.geysermc.floodgate.universal.holder.FloodgateHolder; +import org.geysermc.floodgate.universal.logger.Slf4jLogger; + +public final class FloodgateFabric implements ModInitializer { + private FloodgateHolder holder; + + @Override + public void onInitialize() { + + try { + holder = new UniversalLoader("fabric", FabricLoader.getInstance().getConfigDir().resolve("floodgate"), new Slf4jLogger()).start(); + holder.init(new Class[]{ModInitializer.class}, this); + holder.load(); + } catch (Exception exception) { + throw new RuntimeException("Failed to load Floodgate", exception); + } + + /* + ServerLifecycleEvents.SERVER_STARTED.register(server -> { + holder.enable(); + }); + + ServerLifecycleEvents.SERVER_STOPPING.register(server -> { + holder.disable(); + }); + */ + } +} diff --git a/universal/src/main/resources/fabric.mod.json b/universal/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..17ad61df --- /dev/null +++ b/universal/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "floodgate", + "version": "${version}", + "name": "${name}", + "description": "${description}", + "authors": [ + "${author}" + ], + "contact": { + "website": "${url}", + "repo": "https://github.com/GeyserMC/Floodgate" + }, + "license": "MIT", + "icon": "assets/floodgate/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "org.geysermc.floodgate.FabricMod" + ] + }, + "accessWidener": "floodgate.accesswidener", + "mixins": [ + "floodgate.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14.6", + "fabric": "*", + "minecraft": ">=1.19" + } +} diff --git a/universal/src/main/resources/floodgate.accesswidener b/universal/src/main/resources/floodgate.accesswidener new file mode 100644 index 00000000..4b266731 --- /dev/null +++ b/universal/src/main/resources/floodgate.accesswidener @@ -0,0 +1,6 @@ +accessWidener v1 named + +# To change login state +accessible class net/minecraft/server/network/ServerLoginPacketListenerImpl$State +# For player skin refreshing +accessible class net/minecraft/server/level/ChunkMap$TrackedEntity diff --git a/universal/src/main/resources/floodgate.mixins.json b/universal/src/main/resources/floodgate.mixins.json new file mode 100644 index 00000000..9b980fbf --- /dev/null +++ b/universal/src/main/resources/floodgate.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.floodgate.fabric.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "ChunkMapMixin", + "ClientIntentionPacketMixin", + "ConnectionMixin", + "ServerConnectionListenerMixin", + "ServerLoginPacketListenerImplMixin" + ], + "refmap": "floodgate-fabric-refmap.json", + "injectors": { + "defaultRequire": 1 + } +}