diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c5c15248..b316cd037 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,14 @@ jobs: if: success() with: name: Geyser Fabric - path: bootstrap/fabric/build/libs/Geyser-Fabric.jar + path: bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar + if-no-files-found: error + - name: Archive artifacts (Geyser NeoForge) + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + if: success() + with: + name: Geyser NeoForge + path: bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar if-no-files-found: error - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 @@ -77,6 +84,13 @@ jobs: name: Geyser Velocity path: bootstrap/velocity/build/libs/Geyser-Velocity.jar if-no-files-found: error + - name: Archive artifacts (Geyser ViaProxy) + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + if: success() + with: + name: Geyser ViaProxy + path: bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar + if-no-files-found: error - name: Publish to Maven Repository if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} @@ -106,12 +120,13 @@ jobs: ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$project/$GITHUB_RUN_NUMBER/" # Copy over artifacts rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ # Run the build script # Push the metadata echo "{\"project\": \"$project\", \"version\": \"$version\", \"id\": $GITHUB_RUN_NUMBER, \"commit\": \"$GITHUB_SHA\"}" > metadata.json rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ - - name: Publish to Modrinth + - name: Publish to Modrinth (Fabric) uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} env: @@ -119,7 +134,16 @@ jobs: with: arguments: fabric:modrinth gradle-home-cache-cleanup: true - + + - name: Publish to Modrinth (NeoForge) + uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232 + if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} + env: + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + with: + arguments: neoforge:modrinth + gradle-home-cache-cleanup: true + - name: Notify Discord if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }} # See https://github.com/Tim203/actions-git-discord-webhook/commits diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 797d68767..851c087c1 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -57,7 +57,14 @@ jobs: if: success() with: name: Geyser Fabric - path: geyser/bootstrap/fabric/build/libs/Geyser-Fabric.jar + path: geyser/bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar + if-no-files-found: error + - name: Archive artifacts (Geyser NeoForge) + uses: actions/upload-artifact@v3 + if: success() + with: + name: Geyser NeoForge + path: geyser/bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar if-no-files-found: error - name: Archive artifacts (Geyser Standalone) uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 @@ -87,3 +94,10 @@ jobs: name: Geyser Velocity path: geyser/bootstrap/velocity/build/libs/Geyser-Velocity.jar if-no-files-found: error + - name: Archive artifacts (Geyser ViaProxy) + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 + if: success() + with: + name: Geyser ViaProxy + path: geyser/bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar + if-no-files-found: error diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java index ee5db8242..a3770451a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java +++ b/api/src/main/java/org/geysermc/geyser/api/pack/PathPackCodec.java @@ -42,4 +42,4 @@ public abstract class PathPackCodec extends PackCodec { */ @NonNull public abstract Path path(); -} +} \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java index 815381d6b..cda5e06e4 100644 --- a/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java +++ b/api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java @@ -34,10 +34,12 @@ public record PlatformType(String platformName) { public static final PlatformType ANDROID = new PlatformType("Android"); public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord"); public static final PlatformType FABRIC = new PlatformType("Fabric"); + public static final PlatformType NEOFORGE = new PlatformType("NeoForge"); public static final PlatformType SPIGOT = new PlatformType("Spigot"); @Deprecated public static final PlatformType SPONGE = new PlatformType("Sponge"); public static final PlatformType STANDALONE = new PlatformType("Standalone"); public static final PlatformType VELOCITY = new PlatformType("Velocity"); + public static final PlatformType VIAPROXY = new PlatformType("ViaProxy"); } diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json deleted file mode 100644 index aeb051809..000000000 --- a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "required": true, - "package": "org.geysermc.geyser.platform.fabric.mixin", - "compatibilityLevel": "JAVA_16", - "refmap": "geyser-fabric-refmap.json", - "client": [ - "client.IntegratedServerMixin" - ], - "server": [ - "server.MinecraftDedicatedServerMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/bootstrap/mod/build.gradle.kts b/bootstrap/mod/build.gradle.kts new file mode 100644 index 000000000..7651a2df2 --- /dev/null +++ b/bootstrap/mod/build.gradle.kts @@ -0,0 +1,15 @@ +architectury { + common("neoforge", "fabric") +} + +loom { + mixin.defaultRefmapName.set("geyser-refmap.json") +} + +dependencies { + api(projects.core) + compileOnly(libs.mixin) + + // Only here to suppress "unknown enum constant EnvType.CLIENT" warnings. DO NOT USE! + compileOnly(libs.fabric.loader) +} \ No newline at end of file diff --git a/bootstrap/mod/fabric/build.gradle.kts b/bootstrap/mod/fabric/build.gradle.kts new file mode 100644 index 000000000..dac042ad7 --- /dev/null +++ b/bootstrap/mod/fabric/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + application +} + +architectury { + platformSetupLoomIde() + fabric() +} + +dependencies { + modImplementation(libs.fabric.loader) + modApi(libs.fabric.api) + + api(project(":mod", configuration = "namedElements")) + shadow(project(path = ":mod", configuration = "transformProductionFabric")) { + isTransitive = false + } + shadow(projects.core) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "com.google.code.gson", module = "gson") + exclude(group = "org.slf4j") + exclude(group = "com.nukkitx.fastutil") + exclude(group = "io.netty.incubator") + } + + modImplementation(libs.fabric.permissions) + include(libs.fabric.permissions) +} + +application { + mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") +} + +tasks { + remapJar { + archiveBaseName.set("Geyser-Fabric") + } + + remapModrinthJar { + archiveBaseName.set("geyser-fabric") + } +} + +modrinth { + loaders.add("fabric") + dependencies { + required.project("fabric-api") + } +} \ No newline at end of file diff --git a/bootstrap/mod/fabric/gradle.properties b/bootstrap/mod/fabric/gradle.properties new file mode 100644 index 000000000..90ee7a259 --- /dev/null +++ b/bootstrap/mod/fabric/gradle.properties @@ -0,0 +1 @@ +loom.platform=fabric \ No newline at end of file diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.java new file mode 100644 index 000000000..81e329c03 --- /dev/null +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricBootstrap.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/Geyser + */ + +package org.geysermc.geyser.platform.fabric; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.entity.player.Player; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class GeyserFabricBootstrap extends GeyserModBootstrap implements ModInitializer { + + public GeyserFabricBootstrap() { + super(new GeyserFabricPlatform()); + } + + @Override + public void onInitialize() { + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { + // Set as an event, so we can get the proper IP and port if needed + ServerLifecycleEvents.SERVER_STARTED.register((server) -> { + this.setServer(server); + onGeyserEnable(); + }); + } + + // These are only registered once + ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown()); + ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserModUpdateListener.onPlayReady(handler.getPlayer())); + + this.onGeyserInitialize(); + } + + @Override + public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { + return Permissions.check(source, permissionNode); + } + + @Override + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { + return Permissions.check(source, permissionNode, permissionLevel); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java similarity index 100% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java rename to bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricDumpInfo.java diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java similarity index 100% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java rename to bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMain.java diff --git a/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java new file mode 100644 index 000000000..4631ab493 --- /dev/null +++ b/bootstrap/mod/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricPlatform.java @@ -0,0 +1,99 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.fabric; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Optional; + +public class GeyserFabricPlatform implements GeyserModPlatform { + + private final ModContainer mod; + + public GeyserFabricPlatform() { + this.mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); + } + + @Override + public @NonNull PlatformType platformType() { + return PlatformType.FABRIC; + } + + @Override + public @NonNull String configPath() { + return "Geyser-Fabric"; + } + + @Override + public @NonNull Path dataFolder(@NonNull String modId) { + return FabricLoader.getInstance().getConfigDir().resolve(modId); + } + + @Override + public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) { + return new GeyserFabricDumpInfo(server); + } + + @Override + public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) { + Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); + if (floodgate.isPresent()) { + Path floodgateDataFolder = FabricLoader.getInstance().getConfigDir().resolve("floodgate"); + bootstrap.getGeyserConfig().loadFloodgate(bootstrap, floodgateDataFolder); + return true; + } + + return false; + } + + @Override + public @Nullable InputStream resolveResource(@NonNull String resource) { + // We need to handle this differently, because Fabric shares the classloader across multiple mods + Path path = this.mod.findPath(resource).orElse(null); + if (path == null) { + return null; + } + + try { + return path.getFileSystem() + .provider() + .newInputStream(path); + } catch (IOException e) { + return null; + } + } +} diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json similarity index 74% rename from bootstrap/fabric/src/main/resources/fabric.mod.json rename to bootstrap/mod/fabric/src/main/resources/fabric.mod.json index a192109e2..6bd217433 100644 --- a/bootstrap/fabric/src/main/resources/fabric.mod.json +++ b/bootstrap/mod/fabric/src/main/resources/fabric.mod.json @@ -9,18 +9,18 @@ ], "contact": { "website": "${url}", - "repo": "https://github.com/GeyserMC/Geyser-Fabric" + "repo": "https://github.com/GeyserMC/Geyser" }, "license": "MIT", - "icon": "assets/geyser-fabric/icon.png", + "icon": "assets/geyser/icon.png", "environment": "*", "entrypoints": { "main": [ - "org.geysermc.geyser.platform.fabric.GeyserFabricMod" + "org.geysermc.geyser.platform.fabric.GeyserFabricBootstrap" ] }, "mixins": [ - "geyser-fabric.mixins.json" + "geyser.mixins.json" ], "depends": { "fabricloader": ">=0.15.2", diff --git a/bootstrap/mod/neoforge/build.gradle.kts b/bootstrap/mod/neoforge/build.gradle.kts new file mode 100644 index 000000000..d85087542 --- /dev/null +++ b/bootstrap/mod/neoforge/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + application +} + +architectury { + platformSetupLoomIde() + neoForge() +} + +dependencies { + // See https://github.com/google/guava/issues/6618 + modules { + module("com.google.guava:listenablefuture") { + replacedBy("com.google.guava:guava", "listenablefuture is part of guava") + } + } + + neoForge(libs.neoforge.minecraft) + + api(project(":mod", configuration = "namedElements")) + shadow(project(path = ":mod", configuration = "transformProductionNeoForge")) { + isTransitive = false + } + shadow(projects.core) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "com.google.code.gson", module = "gson") + exclude(group = "org.slf4j") + exclude(group = "io.netty.incubator") + } +} + +application { + mainClass.set("org.geysermc.geyser.platform.forge.GeyserNeoForgeMain") +} + +tasks { + shadowJar { + relocate("it.unimi.dsi.fastutil", "org.geysermc.relocate.fastutil") + } + + remapJar { + archiveBaseName.set("Geyser-NeoForge") + } + + remapModrinthJar { + archiveBaseName.set("geyser-neoforge") + } +} + +modrinth { + loaders.add("neoforge") +} \ No newline at end of file diff --git a/bootstrap/mod/neoforge/gradle.properties b/bootstrap/mod/neoforge/gradle.properties new file mode 100644 index 000000000..2914393db --- /dev/null +++ b/bootstrap/mod/neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge \ No newline at end of file diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java new file mode 100644 index 000000000..67cad1683 --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeBootstrap.java @@ -0,0 +1,83 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.entity.player.Player; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.server.ServerStartedEvent; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.platform.mod.GeyserModUpdateListener; + +@Mod(ModConstants.MOD_ID) +public class GeyserNeoForgeBootstrap extends GeyserModBootstrap { + + private final GeyserNeoForgePermissionHandler permissionHandler = new GeyserNeoForgePermissionHandler(); + + public GeyserNeoForgeBootstrap() { + super(new GeyserNeoForgePlatform()); + + if (FMLLoader.getDist() == Dist.DEDICATED_SERVER) { + // Set as an event so we can get the proper IP and port if needed + NeoForge.EVENT_BUS.addListener(this::onServerStarted); + } + + NeoForge.EVENT_BUS.addListener(this::onServerStopping); + NeoForge.EVENT_BUS.addListener(this::onPlayerJoin); + NeoForge.EVENT_BUS.addListener(this.permissionHandler::onPermissionGather); + + this.onGeyserInitialize(); + } + + private void onServerStarted(ServerStartedEvent event) { + this.setServer(event.getServer()); + this.onGeyserEnable(); + } + + private void onServerStopping(ServerStoppingEvent event) { + this.onGeyserShutdown(); + } + + private void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + GeyserModUpdateListener.onPlayReady(event.getEntity()); + } + + @Override + public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { + return this.permissionHandler.hasPermission(source, permissionNode); + } + + @Override + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { + return this.permissionHandler.hasPermission(source, permissionNode, permissionLevel); + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java new file mode 100644 index 000000000..623f68d3a --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeDumpInfo.java @@ -0,0 +1,84 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.neoforgespi.language.IModInfo; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class GeyserNeoForgeDumpInfo extends BootstrapDumpInfo { + + private final String platformName; + private final String platformVersion; + private final String minecraftVersion; + private final Dist dist; + + @AsteriskSerializer.Asterisk(isIp = true) + private final String serverIP; + private final int serverPort; + private final boolean onlineMode; + private final List mods; + + public GeyserNeoForgeDumpInfo(MinecraftServer server) { + this.platformName = FMLLoader.launcherHandlerName(); + this.platformVersion = FMLLoader.versionInfo().neoForgeVersion(); + this.minecraftVersion = FMLLoader.versionInfo().mcVersion(); + this.dist = FMLLoader.getDist(); + this.serverIP = server.getLocalIp() == null ? "unknown" : server.getLocalIp(); + this.serverPort = server.getPort(); + this.onlineMode = server.usesAuthentication(); + this.mods = new ArrayList<>(); + + for (IModInfo mod : ModList.get().getMods()) { + this.mods.add(new ModInfo( + ModList.get().isLoaded(mod.getModId()), + mod.getModId(), + mod.getVersion().toString(), + mod.getModURL().map(URL::toString).orElse("") + )); + } + } + + @Getter + @AllArgsConstructor + public static class ModInfo { + public boolean enabled; + public String name; + public String version; + public String url; + } +} \ No newline at end of file diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.java new file mode 100644 index 000000000..70bac2a40 --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgeMain.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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +import org.geysermc.geyser.GeyserMain; + +public class GeyserNeoForgeMain extends GeyserMain { + + public static void main(String[] args) { + new GeyserNeoForgeMain().displayMessage(); + } + + @Override + public String getPluginType() { + return "NeoForge"; + } + + @Override + public String getPluginFolder() { + return "mods"; + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java new file mode 100644 index 000000000..0a5f8f052 --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePermissionHandler.java @@ -0,0 +1,149 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.server.permission.PermissionAPI; +import net.neoforged.neoforge.server.permission.events.PermissionGatherEvent; +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionType; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.Constants; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.command.Command; +import org.geysermc.geyser.command.GeyserCommandManager; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; + +public class GeyserNeoForgePermissionHandler { + + private static final Constructor PERMISSION_NODE_CONSTRUCTOR; + + static { + try { + @SuppressWarnings("rawtypes") + Constructor constructor = PermissionNode.class.getDeclaredConstructor( + String.class, + PermissionType.class, + PermissionNode.PermissionResolver.class, + PermissionDynamicContextKey[].class + ); + constructor.setAccessible(true); + PERMISSION_NODE_CONSTRUCTOR = constructor; + } catch (NoSuchMethodException e) { + throw new RuntimeException("Unable to construct PermissionNode!", e); + } + } + + private final Map> permissionNodes = new HashMap<>(); + + public void onPermissionGather(PermissionGatherEvent.Nodes event) { + this.registerNode(Constants.UPDATE_PERMISSION, event); + + GeyserCommandManager commandManager = GeyserImpl.getInstance().commandManager(); + for (Map.Entry entry : commandManager.commands().entrySet()) { + Command command = entry.getValue(); + + // Don't register aliases + if (!command.name().equals(entry.getKey())) { + continue; + } + + this.registerNode(command.permission(), event); + } + + for (Map commands : commandManager.extensionCommands().values()) { + for (Map.Entry entry : commands.entrySet()) { + Command command = entry.getValue(); + + // Don't register aliases + if (!command.name().equals(entry.getKey())) { + continue; + } + + this.registerNode(command.permission(), event); + } + } + } + + public boolean hasPermission(@NonNull Player source, @NonNull String permissionNode) { + PermissionNode node = this.permissionNodes.get(permissionNode); + if (node == null) { + GeyserImpl.getInstance().getLogger().warning("Unable to find permission node " + permissionNode); + return false; + } + + return PermissionAPI.getPermission((ServerPlayer) source, node); + } + + public boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel) { + if (!source.isPlayer()) { + return true; + } + assert source.getPlayer() != null; + boolean permission = this.hasPermission(source.getPlayer(), permissionNode); + if (!permission) { + return source.getPlayer().hasPermissions(permissionLevel); + } + + return true; + } + + private void registerNode(String node, PermissionGatherEvent.Nodes event) { + PermissionNode permissionNode = this.createNode(node); + + // NeoForge likes to crash if you try and register a duplicate node + if (!event.getNodes().contains(permissionNode)) { + event.addNodes(permissionNode); + this.permissionNodes.put(node, permissionNode); + } + } + + @SuppressWarnings("unchecked") + private PermissionNode createNode(String node) { + // The typical constructors in PermissionNode require a + // mod id, which means our permission nodes end up becoming + // geyser_neoforge. instead of just . We work around + // this by using reflection to access the constructor that + // doesn't require a mod id or ResourceLocation. + try { + return (PermissionNode) PERMISSION_NODE_CONSTRUCTOR.newInstance( + node, + PermissionTypes.BOOLEAN, + (PermissionNode.PermissionResolver) (player, playerUUID, context) -> false, + new PermissionDynamicContextKey[0] + ); + } catch (Exception e) { + throw new RuntimeException("Unable to create permission node " + node, e); + } + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.java new file mode 100644 index 000000000..63abe4a4a --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/GeyserNeoForgePlatform.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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +import net.minecraft.server.MinecraftServer; +import net.neoforged.fml.loading.FMLPaths; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; + +import java.io.InputStream; +import java.nio.file.Path; + +public class GeyserNeoForgePlatform implements GeyserModPlatform { + + @Override + public @NonNull PlatformType platformType() { + return PlatformType.NEOFORGE; + } + + @Override + public @NonNull String configPath() { + return "Geyser-NeoForge"; + } + + @Override + public @NonNull Path dataFolder(@NonNull String modId) { + return FMLPaths.CONFIGDIR.get().resolve(modId); + } + + @Override + public @NonNull BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server) { + return new GeyserNeoForgeDumpInfo(server); + } + + @Override + public boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap) { + return false; // No Floodgate mod for NeoForge yet + } + + @Override + public @Nullable InputStream resolveResource(@NonNull String resource) { + return GeyserBootstrap.class.getClassLoader().getResourceAsStream(resource); + } +} diff --git a/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java new file mode 100644 index 000000000..aa72bb2a0 --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/java/org/geysermc/geyser/platform/neoforge/ModConstants.java @@ -0,0 +1,30 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.neoforge; + +public class ModConstants { + public static final String MOD_ID = "geyser_neoforge"; +} diff --git a/bootstrap/mod/neoforge/src/main/resources/META-INF/mods.toml b/bootstrap/mod/neoforge/src/main/resources/META-INF/mods.toml new file mode 100644 index 000000000..2f9e9b12e --- /dev/null +++ b/bootstrap/mod/neoforge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,25 @@ +modLoader="javafml" +loaderVersion="[1,)" +license="MIT" +[[mods]] +modId="geyser_neoforge" +version="${version}" +displayName="Geyser" +displayURL="https://geysermc.org/" +logoFile= "../assets/geyser/icon.png" +authors="GeyserMC" +description="${description}" +[[mixins]] +config = "geyser.mixins.json" +[[dependencies.geyser_neoforge]] + modId="neoforge" + type="required" + versionRange="[20.4.48-beta,)" + ordering="NONE" + side="BOTH" +[[dependencies.geyser_neoforge]] + modId="minecraft" + type="required" + versionRange="[1.20,1.21)" + ordering="NONE" + side="BOTH" \ No newline at end of file diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java new file mode 100644 index 000000000..8dc0026bf --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserChannelGetter.java @@ -0,0 +1,43 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.mod; + +import io.netty.channel.ChannelFuture; + +import java.util.List; + +/** + * Represents a getter to the server channels in the connection listener class. + */ +public interface GeyserChannelGetter { + + /** + * Returns the channels. + * + * @return The channels. + */ + List geyser$getChannels(); +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java similarity index 70% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java index 339edab5b..2ea40a94b 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricMod.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModBootstrap.java @@ -23,21 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.Setter; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.server.MinecraftServer; +import net.minecraft.world.entity.player.Player; import org.apache.logging.log4j.LogManager; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,7 +42,6 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; @@ -54,8 +49,9 @@ import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.geysermc.geyser.platform.fabric.command.GeyserFabricCommandExecutor; -import org.geysermc.geyser.platform.fabric.world.GeyserFabricWorldManager; +import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor; +import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; +import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -64,58 +60,46 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.Map; -import java.util.Optional; import java.util.UUID; -public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { +@RequiredArgsConstructor +public abstract class GeyserModBootstrap implements GeyserBootstrap { @Getter - private static GeyserFabricMod instance; + private static GeyserModBootstrap instance; + + private final GeyserModPlatform platform; + private GeyserImpl geyser; - private ModContainer mod; private Path dataFolder; @Setter private MinecraftServer server; private GeyserCommandManager geyserCommandManager; - private GeyserFabricConfiguration geyserConfig; - private GeyserFabricLogger geyserLogger; + private GeyserModConfiguration geyserConfig; + private GeyserModInjector geyserInjector; + private GeyserModLogger geyserLogger; private IGeyserPingPassthrough geyserPingPassthrough; private WorldManager geyserWorldManager; - @Override - public void onInitialize() { - instance = this; - mod = FabricLoader.getInstance().getModContainer("geyser-fabric").orElseThrow(); - onGeyserInitialize(); - } - @Override public void onGeyserInitialize() { - if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) { - // Set as an event, so we can get the proper IP and port if needed - ServerLifecycleEvents.SERVER_STARTED.register((server) -> { - this.server = server; - onGeyserEnable(); - }); - } - - // These are only registered once - ServerLifecycleEvents.SERVER_STOPPING.register((server) -> onGeyserShutdown()); - ServerPlayConnectionEvents.JOIN.register((handler, $, $$) -> GeyserFabricUpdateListener.onPlayReady(handler)); - - dataFolder = FabricLoader.getInstance().getConfigDir().resolve("Geyser-Fabric"); + instance = this; + dataFolder = this.platform.dataFolder(this.platform.configPath()); GeyserLocale.init(this); if (!loadConfig()) { return; } - this.geyserLogger = new GeyserFabricLogger(geyserConfig.isDebugMode()); + this.geyserLogger = new GeyserModLogger(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - this.geyser = GeyserImpl.load(PlatformType.FABRIC, this, null); + this.geyser = GeyserImpl.load(this.platform.platformType(), this, null); + + // Create command manager here, since the permission handler on neo needs it + this.geyserCommandManager = new GeyserCommandManager(geyser); + this.geyserCommandManager.init(); } - @Override public void onGeyserEnable() { if (GeyserImpl.getInstance().isReloading()) { if (!loadConfig()) { @@ -123,35 +107,37 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } this.geyserLogger.setDebug(geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - } else { - this.geyserCommandManager = new GeyserCommandManager(geyser); - this.geyserCommandManager.init(); } + GeyserImpl.start(); + if (geyserConfig.isLegacyPingPassthrough()) { this.geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser); } else { this.geyserPingPassthrough = new ModPingPassthrough(server, geyserLogger); } - GeyserImpl.start(); - - // No need to re-register commands, or re-recreate the world manager when reloading + // No need to re-register commands, or try to re-inject if (GeyserImpl.getInstance().isReloading()) { return; } - this.geyserWorldManager = new GeyserFabricWorldManager(server); + this.geyserWorldManager = new GeyserModWorldManager(server); + + // We want to do this late in the server startup process to allow other mods + // To do their job injecting, then connect into *that* + this.geyserInjector = new GeyserModInjector(server, this.platform); + this.geyserInjector.initializeLocalChannel(this); // Start command building // Set just "geyser" as the help command - GeyserFabricCommandExecutor helpExecutor = new GeyserFabricCommandExecutor(geyser, + GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser, (GeyserCommand) geyser.commandManager().getCommands().get("help")); LiteralArgumentBuilder builder = Commands.literal("geyser").executes(helpExecutor); // Register all subcommands as valid for (Map.Entry command : geyser.commandManager().getCommands().entrySet()) { - GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue()); + GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue()); builder.then(Commands.literal(command.getKey()) .executes(executor) // Could also test for Bedrock but depending on when this is called it may backfire @@ -171,12 +157,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { } // Register help command for just "/" - GeyserFabricCommandExecutor extensionHelpExecutor = new GeyserFabricCommandExecutor(geyser, + GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser, (GeyserCommand) extensionCommands.get("help")); LiteralArgumentBuilder extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor); for (Map.Entry command : extensionCommands.entrySet()) { - GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue()); + GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue()); extCmdBuilder.then(Commands.literal(command.getKey()) .executes(executor) .requires(executor::testPermission) @@ -201,11 +187,14 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { geyser.shutdown(); geyser = null; } - this.server = null; + if (geyserInjector != null) { + geyserInjector.shutdown(); + this.server = null; + } } @Override - public GeyserConfiguration getGeyserConfig() { + public GeyserModConfiguration getGeyserConfig() { return geyserConfig; } @@ -236,7 +225,7 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @Override public BootstrapDumpInfo getDumpInfo() { - return new GeyserFabricDumpInfo(server); + return this.platform.dumpInfo(this.server); } @Override @@ -258,32 +247,19 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { @Override public boolean testFloodgatePluginPresent() { - Optional floodgate = FabricLoader.getInstance().getModContainer("floodgate"); - if (floodgate.isPresent()) { - geyserConfig.loadFloodgate(this, floodgate.orElse(null)); - return true; - } - return false; + return this.platform.testFloodgatePluginPresent(this); } @Nullable @Override public InputStream getResourceOrNull(String resource) { - // We need to handle this differently, because Fabric shares the classloader across multiple mods - Path path = this.mod.findPath(resource).orElse(null); - if (path == null) { - return null; - } - - try { - return path.getFileSystem() - .provider() - .newInputStream(path); - } catch (IOException e) { - return null; - } + return this.platform.resolveResource(resource); } + public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode); + + public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel); + @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean loadConfig() { try { @@ -294,10 +270,10 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap { File configFile = FileUtils.fileOrCopiedFromResource(dataFolder.resolve("config.yml").toFile(), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); - this.geyserConfig = FileUtils.loadConfig(configFile, GeyserFabricConfiguration.class); + this.geyserConfig = FileUtils.loadConfig(configFile, GeyserModConfiguration.class); return true; } catch (IOException ex) { - LogManager.getLogger("geyser-fabric").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + LogManager.getLogger("geyser").error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); return false; } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java new file mode 100644 index 000000000..631a21510 --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModCompressionDisabler.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2021 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/Geyser + */ + +package org.geysermc.geyser.platform.mod; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import net.minecraft.network.protocol.login.ClientboundGameProfilePacket; +import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket; + +/** + * Disables the compression packet (and the compression handlers from being added to the pipeline) for Geyser clients + * that won't be receiving the data over the network. + *

+ * As of 1.8 - 1.17.1, compression is enabled in the Netty pipeline by adding a listener after a packet is written. + * If we simply "cancel" or don't forward the packet, then the listener is never called. + */ +public class GeyserModCompressionDisabler extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + Class msgClass = msg.getClass(); + // Don't let any compression packet get through + if (!ClientboundLoginCompressionPacket.class.isAssignableFrom(msgClass)) { + if (ClientboundGameProfilePacket.class.isAssignableFrom(msgClass)) { + + // We're past the point that a compression packet can be sent, so we can safely yeet ourselves away + ctx.channel().pipeline().remove(this); + } + super.write(ctx, msg, promise); + } + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java similarity index 80% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java index f557d16c0..a24380bd6 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricConfiguration.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModConfiguration.java @@ -23,23 +23,20 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; import com.fasterxml.jackson.annotation.JsonIgnore; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; import org.geysermc.geyser.FloodgateKeyLoader; import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; import java.nio.file.Path; -public class GeyserFabricConfiguration extends GeyserJacksonConfiguration { +public class GeyserModConfiguration extends GeyserJacksonConfiguration { @JsonIgnore private Path floodgateKeyPath; - public void loadFloodgate(GeyserFabricMod geyser, ModContainer floodgate) { + public void loadFloodgate(GeyserModBootstrap geyser, Path floodgateDataFolder) { Path geyserDataFolder = geyser.getConfigFolder(); - Path floodgateDataFolder = floodgate != null ? FabricLoader.getInstance().getConfigDir().resolve("floodgate") : null; floodgateKeyPath = FloodgateKeyLoader.getKeyPath(this, floodgateDataFolder, geyserDataFolder, geyser.getGeyserLogger()); } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java new file mode 100644 index 000000000..06496293f --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModInjector.java @@ -0,0 +1,156 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.mod; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.DefaultEventLoopGroup; +import io.netty.channel.local.LocalAddress; +import io.netty.util.concurrent.DefaultThreadFactory; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerConnectionListener; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.network.netty.GeyserInjector; +import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; +import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +public class GeyserModInjector extends GeyserInjector { + + private final MinecraftServer server; + private final GeyserModPlatform platform; + private DefaultEventLoopGroup eventLoopGroup; + + /** + * Used to uninject ourselves on shutdown. + */ + private List allServerChannels; + + public GeyserModInjector(MinecraftServer server, GeyserModPlatform platform) { + this.server = server; + this.platform = platform; + } + + @Override + protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception { + ServerConnectionListener connection = this.server.getConnection(); + + // Find the channel that Minecraft uses to listen to connections + ChannelFuture listeningChannel = null; + this.allServerChannels = ((GeyserChannelGetter) connection).geyser$getChannels(); + for (ChannelFuture o : allServerChannels) { + listeningChannel = o; + break; + } + + if (listeningChannel == null) { + throw new RuntimeException("Unable to find listening channel!"); + } + + // Making this a function prevents childHandler from being treated as a non-final variable + ChannelInitializer childHandler = getChildHandler(bootstrap, listeningChannel); + // This method is what initializes the connection in Java Edition, after Netty is all set. + Method initChannel = childHandler.getClass().getDeclaredMethod("initChannel", Channel.class); + initChannel.setAccessible(true); + + // Separate variable so we can shut it down later + eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser " + this.platform.platformType().platformName() + " connection thread", Thread.MAX_PRIORITY)); + ChannelFuture channelFuture = (new ServerBootstrap() + .channel(LocalServerChannelWrapper.class) + .childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(@NonNull Channel ch) throws Exception { + initChannel.invoke(childHandler, ch); + + if (bootstrap.getGeyserConfig().isDisableCompression()) { + ch.pipeline().addAfter("encoder", "geyser-compression-disabler", new GeyserModCompressionDisabler()); + } + } + }) + // Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default + .group(eventLoopGroup) + .localAddress(LocalAddress.ANY)) + .bind() + .syncUninterruptibly(); + // We don't need to add to the list, but plugins like ProtocolSupport and ProtocolLib that add to the main pipeline + // will work when we add to the list. + allServerChannels.add(channelFuture); + this.localChannel = channelFuture; + this.serverSocketAddress = channelFuture.channel().localAddress(); + } + + @SuppressWarnings("unchecked") + private ChannelInitializer getChildHandler(GeyserBootstrap bootstrap, ChannelFuture listeningChannel) { + List names = listeningChannel.channel().pipeline().names(); + ChannelInitializer childHandler = null; + for (String name : names) { + ChannelHandler handler = listeningChannel.channel().pipeline().get(name); + try { + Field childHandlerField = handler.getClass().getDeclaredField("childHandler"); + childHandlerField.setAccessible(true); + childHandler = (ChannelInitializer) childHandlerField.get(handler); + break; + } catch (Exception e) { + if (bootstrap.getGeyserConfig().isDebugMode()) { + bootstrap.getGeyserLogger().debug("The handler " + name + " isn't a ChannelInitializer. THIS ERROR IS SAFE TO IGNORE!"); + e.printStackTrace(); + } + } + } + if (childHandler == null) { + throw new RuntimeException(); + } + return childHandler; + } + + @Override + public void shutdown() { + if (this.allServerChannels != null) { + this.allServerChannels.remove(this.localChannel); + this.allServerChannels = null; + } + + if (eventLoopGroup != null) { + try { + eventLoopGroup.shutdownGracefully().sync(); + eventLoopGroup = null; + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error("Unable to shut down injector! " + e.getMessage()); + e.printStackTrace(); + } + } + + super.shutdown(); + } +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java similarity index 90% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java index 180197f2d..444b725e9 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricLogger.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -32,12 +32,12 @@ import org.apache.logging.log4j.Logger; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.text.ChatColor; -public class GeyserFabricLogger implements GeyserLogger { - private final Logger logger = LogManager.getLogger("geyser-fabric"); +public class GeyserModLogger implements GeyserLogger { + private final Logger logger = LogManager.getLogger("geyser"); private boolean debug; - public GeyserFabricLogger(boolean isDebug) { + public GeyserModLogger(boolean isDebug) { debug = isDebug; } @@ -73,7 +73,7 @@ public class GeyserFabricLogger implements GeyserLogger { @Override public void sendMessage(Component message) { - // As of Java Edition 1.19.2, Fabric's console doesn't natively support legacy format + // As of Java Edition 1.19.2, Minecraft's console doesn't natively support legacy format String flattened = LegacyComponentSerializer.legacySection().serialize(message); // Add the reset at the end, or else format will persist... forever. // https://cdn.discordapp.com/attachments/573909525132738590/1033904509170225242/unknown.png diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java similarity index 68% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java index 1ea69cbe2..11ca0bc4f 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserFabricUpdateListener.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModUpdateListener.java @@ -23,21 +23,22 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.world.entity.player.Player; import org.geysermc.geyser.Constants; -import org.geysermc.geyser.platform.fabric.command.FabricCommandSender; +import org.geysermc.geyser.platform.mod.command.ModCommandSender; import org.geysermc.geyser.util.VersionCheckUtils; -public final class GeyserFabricUpdateListener { - public static void onPlayReady(ServerGamePacketListenerImpl handler) { - if (Permissions.check(handler.player, Constants.UPDATE_PERMISSION, 2)) { - VersionCheckUtils.checkForGeyserUpdate(() -> new FabricCommandSender(handler.player.createCommandSourceStack())); +public final class GeyserModUpdateListener { + public static void onPlayReady(Player player) { + CommandSourceStack stack = player.createCommandSourceStack(); + if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) { + VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack)); } } - private GeyserFabricUpdateListener() { + private GeyserModUpdateListener() { } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java similarity index 97% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java index 4f1c8b638..fad0d1678 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/GeyserServerPortGetter.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserServerPortGetter.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; import net.minecraft.server.MinecraftServer; diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java similarity index 94% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java index e74be7fb7..12d690d83 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/ModPingPassthrough.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/ModPingPassthrough.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric; +package org.geysermc.geyser.platform.mod; import lombok.AllArgsConstructor; import net.kyori.adventure.text.Component; @@ -39,10 +39,11 @@ import net.minecraft.network.protocol.status.ServerStatusPacketListener; import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerStatusPacketListenerImpl; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.ping.GeyserPingInfo; import org.geysermc.geyser.ping.IGeyserPingPassthrough; -import org.jetbrains.annotations.Nullable; import java.net.InetSocketAddress; import java.util.Objects; @@ -100,7 +101,7 @@ public class ModPingPassthrough implements IGeyserPingPassthrough { } @Override - public void send(Packet packet, @Nullable PacketSendListener packetSendListener, boolean bl) { + public void send(@NonNull Packet packet, @Nullable PacketSendListener packetSendListener, boolean bl) { if (packet instanceof ClientboundStatusResponsePacket statusResponse) { status = statusResponse.status(); } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java similarity index 82% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java index 86b50d431..694dc732e 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/GeyserFabricCommandExecutor.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/GeyserModCommandExecutor.java @@ -23,31 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric.command; +package org.geysermc.geyser.platform.mod.command; import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; -import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.commands.CommandSourceStack; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommand; import org.geysermc.geyser.command.GeyserCommandExecutor; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import java.util.Collections; -public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command { +public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command { private final GeyserCommand command; - public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command) { - super(connector, Collections.singletonMap(command.name(), command)); + public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) { + super(geyser, Collections.singletonMap(command.name(), command)); this.command = command; } public boolean testPermission(CommandSourceStack source) { - return Permissions.check(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); + return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0); } @Override @@ -57,7 +57,7 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement public int runWithArgs(CommandContext context, String args) { CommandSourceStack source = context.getSource(); - FabricCommandSender sender = new FabricCommandSender(source); + ModCommandSender sender = new ModCommandSender(source); GeyserSession session = getGeyserSession(sender); if (!testPermission(source)) { sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale())); diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java similarity index 88% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java index 28875ec6e..17154ffd8 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/command/FabricCommandSender.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/command/ModCommandSender.java @@ -23,9 +23,8 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric.command; +package org.geysermc.geyser.platform.mod.command; -import me.lucko.fabric.api.permissions.v0.Permissions; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.minecraft.commands.CommandSourceStack; import net.minecraft.network.chat.Component; @@ -33,15 +32,16 @@ import net.minecraft.server.level.ServerPlayer; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.GeyserCommandSource; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.text.ChatColor; import java.util.Objects; -public class FabricCommandSender implements GeyserCommandSource { +public class ModCommandSender implements GeyserCommandSource { private final CommandSourceStack source; - public FabricCommandSender(CommandSourceStack source) { + public ModCommandSender(CommandSourceStack source) { this.source = source; } @@ -76,6 +76,6 @@ public class FabricCommandSender implements GeyserCommandSource { @Override public boolean hasPermission(String permission) { - return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel()); + return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel()); } } diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java similarity index 85% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java index 999a077bb..4db1165fc 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/client/IntegratedServerMixin.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/client/IntegratedServerMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * 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 @@ -23,18 +23,16 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric.mixin.client; +package org.geysermc.geyser.platform.mod.mixin.client; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.server.IntegratedServer; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.GameType; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.platform.fabric.GeyserFabricMod; -import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; +import org.geysermc.geyser.platform.mod.GeyserServerPortGetter; import org.geysermc.geyser.text.GeyserLocale; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -45,7 +43,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Objects; -@Environment(EnvType.CLIENT) @Mixin(IntegratedServer.class) public class IntegratedServerMixin implements GeyserServerPortGetter { @Shadow @@ -57,8 +54,8 @@ public class IntegratedServerMixin implements GeyserServerPortGetter { private void onOpenToLan(GameType gameType, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) { if (cir.getReturnValueZ()) { // If the LAN is opened, starts Geyser. - GeyserFabricMod.getInstance().setServer((MinecraftServer) (Object) this); - GeyserFabricMod.getInstance().onGeyserEnable(); + GeyserModBootstrap.getInstance().setServer((MinecraftServer) (Object) this); + GeyserModBootstrap.getInstance().onGeyserEnable(); // Ensure player locale has been loaded, in case it's different from Java system language GeyserLocale.loadGeyserLocale(this.minecraft.options.languageCode); // Give indication that Geyser is loaded diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java similarity index 76% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java index 23e148775..3b809d321 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/DedicatedServerMixin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * 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 @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric.mixin.server; +package org.geysermc.geyser.platform.mod.mixin.server; import com.mojang.datafixers.DataFixer; import net.minecraft.server.MinecraftServer; @@ -33,14 +33,14 @@ import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.level.progress.ChunkProgressListenerFactory; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.world.level.storage.LevelStorageSource; -import org.geysermc.geyser.platform.fabric.GeyserServerPortGetter; +import org.geysermc.geyser.platform.mod.GeyserServerPortGetter; import org.spongepowered.asm.mixin.Mixin; import java.net.Proxy; @Mixin(DedicatedServer.class) -public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { - public MinecraftDedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) { +public abstract class DedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter { + public DedicatedServerMixin(Thread thread, LevelStorageSource.LevelStorageAccess levelStorageAccess, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer dataFixer, Services services, ChunkProgressListenerFactory chunkProgressListenerFactory) { super(thread, levelStorageAccess, packRepository, worldStem, proxy, dataFixer, services, chunkProgressListenerFactory); } diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java new file mode 100644 index 000000000..52941d631 --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/mixin/server/ServerConnectionListenerMixin.java @@ -0,0 +1,46 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.mod.mixin.server; + +import io.netty.channel.ChannelFuture; +import net.minecraft.server.network.ServerConnectionListener; +import org.geysermc.geyser.platform.mod.GeyserChannelGetter; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(ServerConnectionListener.class) +public abstract class ServerConnectionListenerMixin implements GeyserChannelGetter { + + @Shadow @Final private List channels; + + @Override + public List geyser$getChannels() { + return this.channels; + } +} diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java new file mode 100644 index 000000000..2f615591b --- /dev/null +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/platform/GeyserModPlatform.java @@ -0,0 +1,92 @@ +/* + * 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/Geyser + */ + +package org.geysermc.geyser.platform.mod.platform; + +import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; + +import java.io.InputStream; +import java.nio.file.Path; + +/** + * An interface which holds common methods that have different + * APIs on their respective mod platforms. + */ +public interface GeyserModPlatform { + + /** + * Gets the {@link PlatformType} of the mod platform. + * + * @return the platform type of the mod platform + */ + @NonNull + PlatformType platformType(); + + /** + * Gets the config path of the mod platform. + * + * @return the config path of the mod platform + */ + @NonNull + String configPath(); + + /** + * Gets the data folder of the mod platform. + * + * @return the data folder of the mod platform + */ + @NonNull + Path dataFolder(@NonNull String modId); + + /** + * Gets the dump info of the mod platform. + * + * @param server the server to get the dump info from + * @return the dump info of the mod platform + */ + @NonNull + BootstrapDumpInfo dumpInfo(@NonNull MinecraftServer server); + + /** + * Tests if the Floodgate plugin is present on the mod platform. + * + * @return {@code true} if the Floodgate plugin is present on the mod platform, {@code false} otherwise + */ + boolean testFloodgatePluginPresent(@NonNull GeyserModBootstrap bootstrap); + + /** + * Resolves a resource from the mod jar. + * + * @param resource the name of the resource + * @return the input stream of the resource + */ + @Nullable + InputStream resolveResource(@NonNull String resource); +} diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java similarity index 81% rename from bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java rename to bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java index 9d7b81831..04c538632 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/world/GeyserModWorldManager.java @@ -23,22 +23,41 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.platform.fabric.world; +package org.geysermc.geyser.platform.mod.world; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo; -import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.*; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.EndTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagVisitor; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.WritableBookItem; import net.minecraft.world.item.WrittenBookItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BannerBlockEntity; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.LecternBlockEntity; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.nbt.NbtMap; @@ -46,6 +65,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.nbt.NbtType; import org.geysermc.erosion.util.LecternUtils; import org.geysermc.geyser.level.GeyserWorldManager; +import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.platform.mod.GeyserModBootstrap; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.BlockEntityUtils; @@ -54,13 +75,54 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; -public class GeyserFabricWorldManager extends GeyserWorldManager { +public class GeyserModWorldManager extends GeyserWorldManager { private final MinecraftServer server; - public GeyserFabricWorldManager(MinecraftServer server) { + public GeyserModWorldManager(MinecraftServer server) { this.server = server; } + @Override + public int getBlockAt(GeyserSession session, int x, int y, int z) { + // If the protocol version of Geyser and the server are not the + // same, fallback to the chunk cache. May be able to update this + // in the future to use ViaVersion however, like Spigot does. + if (SharedConstants.getCurrentVersion().getProtocolVersion() != GameProtocol.getJavaProtocolVersion()) { + return super.getBlockAt(session, x, y, z); + } + + ServerPlayer player = this.getPlayer(session); + if (player == null) { + return 0; + } + + Level level = player.level(); + if (y < level.getMinBuildHeight()) { + return 0; + } + + ChunkAccess chunk = level.getChunkSource().getChunk(x >> 4, z >> 4, ChunkStatus.FULL, false); + if (chunk == null) { + return 0; + } + + int worldOffset = level.getMinBuildHeight() >> 4; + int chunkOffset = (y >> 4) - worldOffset; + if (chunkOffset < chunk.getSections().length) { + LevelChunkSection section = chunk.getSections()[chunkOffset]; + if (section != null && !section.hasOnlyAir()) { + return Block.getId(section.getBlockState(x & 15, y & 15, z & 15)); + } + } + + return 0; + } + + @Override + public boolean hasOwnChunkCache() { + return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion(); + } + @Override public boolean shouldExpectLecternHandled(GeyserSession session) { return true; @@ -154,7 +216,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager { @Override public boolean hasPermission(GeyserSession session, String permission) { ServerPlayer player = getPlayer(session); - return Permissions.check(player, permission); + return GeyserModBootstrap.getInstance().hasPermission(player, permission); } @Override diff --git a/bootstrap/mod/src/main/resources/geyser.mixins.json b/bootstrap/mod/src/main/resources/geyser.mixins.json new file mode 100644 index 000000000..47b2f60f3 --- /dev/null +++ b/bootstrap/mod/src/main/resources/geyser.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.geyser.platform.mod.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "server.ServerConnectionListenerMixin" + ], + "server": [ + "server.DedicatedServerMixin" + ], + "client": [ + "client.IntegratedServerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index 41b00b7f3..8bd384f69 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -100,7 +100,7 @@ public class GeyserStandaloneGUI { Container cp = frame.getContentPane(); // Fetch and set the icon for the frame - URL image = getClass().getClassLoader().getResource("icon.png"); + URL image = getClass().getClassLoader().getResource("assets/geyser/icon.png"); if (image != null) { ImageIcon icon = new ImageIcon(image); frame.setIconImage(icon.getImage()); diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts new file mode 100644 index 000000000..4d5d4f949 --- /dev/null +++ b/bootstrap/viaproxy/build.gradle.kts @@ -0,0 +1,26 @@ +dependencies { + api(projects.core) +} + +platformRelocate("net.kyori") +platformRelocate("org.yaml") +platformRelocate("it.unimi.dsi.fastutil") +platformRelocate("org.cloudburstmc.netty") + +// These dependencies are already present on the platform +provided(libs.viaproxy) + +application { + mainClass.set("org.geysermc.geyser.platform.viaproxy.GeyserViaProxyMain") +} + +tasks.withType { + archiveBaseName.set("Geyser-ViaProxy") + + dependencies { + exclude(dependency("com.google.*:.*")) + exclude(dependency("io.netty:.*")) + exclude(dependency("org.slf4j:.*")) + exclude(dependency("org.ow2.asm:.*")) + } +} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java new file mode 100644 index 000000000..ad249eb3b --- /dev/null +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyConfiguration.java @@ -0,0 +1,53 @@ +/* + * 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/Geyser + */ +package org.geysermc.geyser.platform.viaproxy; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import net.raphimc.vialegacy.api.LegacyProtocolVersion; +import net.raphimc.viaproxy.cli.options.Options; +import org.geysermc.geyser.configuration.GeyserJacksonConfiguration; + +import java.io.File; +import java.nio.file.Path; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class GeyserViaProxyConfiguration extends GeyserJacksonConfiguration { + + @Override + public Path getFloodgateKeyPath() { + return new File(GeyserViaProxyPlugin.ROOT_FOLDER, this.getFloodgateKeyFile()).toPath(); + } + + @Override + public int getPingPassthroughInterval() { + int interval = super.getPingPassthroughInterval(); + if (interval < 15 && Options.PROTOCOL_VERSION != null && Options.PROTOCOL_VERSION.olderThanOrEqualTo(LegacyProtocolVersion.r1_6_4)) { + // <= 1.6.4 servers sometimes block incoming connections from an IP address if too many connections are made + interval = 15; + } + return interval; + } + +} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java new file mode 100644 index 000000000..08f3d5371 --- /dev/null +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyDumpInfo.java @@ -0,0 +1,67 @@ +/* + * 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/Geyser + */ +package org.geysermc.geyser.platform.viaproxy; + +import lombok.Getter; +import net.raphimc.viaproxy.ViaProxy; +import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.plugins.ViaProxyPlugin; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.text.AsteriskSerializer; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +public class GeyserViaProxyDumpInfo extends BootstrapDumpInfo { + + private final String platformVersion; + private final boolean onlineMode; + + @AsteriskSerializer.Asterisk(isIp = true) + private final String serverIP; + private final int serverPort; + private final List plugins; + + public GeyserViaProxyDumpInfo() { + this.platformVersion = ViaProxy.VERSION; + this.onlineMode = Options.ONLINE_MODE; + if (Options.BIND_ADDRESS instanceof InetSocketAddress inetSocketAddress) { + this.serverIP = inetSocketAddress.getHostString(); + this.serverPort = inetSocketAddress.getPort(); + } else { + this.serverIP = "unsupported"; + this.serverPort = 0; + } + this.plugins = new ArrayList<>(); + + for (ViaProxyPlugin plugin : ViaProxy.getPluginManager().getPlugins()) { + this.plugins.add(new PluginInfo(true, plugin.getName(), plugin.getVersion(), "unknown", Collections.singletonList(plugin.getAuthor()))); + } + } + +} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java new file mode 100644 index 000000000..10f414b51 --- /dev/null +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyLogger.java @@ -0,0 +1,88 @@ +/* + * 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/Geyser + */ +package org.geysermc.geyser.platform.viaproxy; + +import net.raphimc.viaproxy.cli.ConsoleFormatter; +import org.apache.logging.log4j.Logger; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.command.GeyserCommandSource; + +public class GeyserViaProxyLogger implements GeyserLogger, GeyserCommandSource { + + private final Logger logger; + private boolean debug; + + public GeyserViaProxyLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void severe(String message) { + this.logger.fatal(ConsoleFormatter.convert(message)); + } + + @Override + public void severe(String message, Throwable error) { + this.logger.fatal(ConsoleFormatter.convert(message), error); + } + + @Override + public void error(String message) { + this.logger.error(ConsoleFormatter.convert(message)); + } + + @Override + public void error(String message, Throwable error) { + this.logger.error(ConsoleFormatter.convert(message), error); + } + + @Override + public void warning(String message) { + this.logger.warn(ConsoleFormatter.convert(message)); + } + + @Override + public void info(String message) { + this.logger.info(ConsoleFormatter.convert(message)); + } + + @Override + public void debug(String message) { + if (this.debug) { + this.logger.debug(ConsoleFormatter.convert(message)); + } + } + + @Override + public void setDebug(boolean debug) { + this.debug = debug; + } + + @Override + public boolean isDebug() { + return this.debug; + } + +} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.java new file mode 100644 index 000000000..675c92534 --- /dev/null +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyMain.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/Geyser + */ + +package org.geysermc.geyser.platform.viaproxy; + +import net.raphimc.viaproxy.plugins.PluginManager; +import org.geysermc.geyser.GeyserMain; + +public class GeyserViaProxyMain extends GeyserMain { + + public static void main(String[] args) { + new GeyserViaProxyMain().displayMessage(); + } + + public String getPluginType() { + return "ViaProxy"; + } + + public String getPluginFolder() { + return PluginManager.PLUGINS_DIR.getName(); + } + +} diff --git a/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java new file mode 100644 index 000000000..47745df7d --- /dev/null +++ b/bootstrap/viaproxy/src/main/java/org/geysermc/geyser/platform/viaproxy/GeyserViaProxyPlugin.java @@ -0,0 +1,224 @@ +/* + * 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/Geyser + */ +package org.geysermc.geyser.platform.viaproxy; + +import net.lenni0451.lambdaevents.EventHandler; +import net.raphimc.vialegacy.api.LegacyProtocolVersion; +import net.raphimc.viaproxy.ViaProxy; +import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.plugins.PluginManager; +import net.raphimc.viaproxy.plugins.ViaProxyPlugin; +import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; +import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; +import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; +import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; +import org.apache.logging.log4j.LogManager; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.util.PlatformType; +import org.geysermc.geyser.command.GeyserCommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; +import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.util.LoopbackUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootstrap { + + public static final File ROOT_FOLDER = new File(PluginManager.PLUGINS_DIR, "Geyser"); + + private final GeyserViaProxyLogger logger = new GeyserViaProxyLogger(LogManager.getLogger("Geyser")); + private GeyserViaProxyConfiguration config; + private GeyserImpl geyser; + private GeyserCommandManager commandManager; + private IGeyserPingPassthrough pingPassthrough; + + @Override + public void onEnable() { + ROOT_FOLDER.mkdirs(); + + GeyserLocale.init(this); + this.onGeyserInitialize(); + + ViaProxy.EVENT_MANAGER.register(this); + } + + @Override + public void onDisable() { + this.onGeyserShutdown(); + } + + @EventHandler + private void onConsoleCommand(final ConsoleCommandEvent event) { + final String command = event.getCommand().startsWith("/") ? event.getCommand().substring(1) : event.getCommand(); + if (this.getGeyserCommandManager().runCommand(this.getGeyserLogger(), command + " " + String.join(" ", event.getArgs()))) { + event.setCancelled(true); + } + } + + @EventHandler + private void onShouldVerifyOnlineModeEvent(final ShouldVerifyOnlineModeEvent event) { + final UUID uuid = event.getProxyConnection().getGameProfile().getId(); + if (uuid == null) return; + + final GeyserSession connection = GeyserImpl.getInstance().onlineConnections().stream().filter(s -> s.javaUuid().equals(uuid)).findAny().orElse(null); + if (connection == null) return; + + if (connection.javaUsername().equals(event.getProxyConnection().getGameProfile().getName())) { + event.setCancelled(true); + } + } + + @EventHandler + private void onProxyStart(final ProxyStartEvent event) { + this.onGeyserEnable(); + } + + @EventHandler + private void onProxyStop(final ProxyStopEvent event) { + this.onGeyserDisable(); + } + + @Override + public void onGeyserInitialize() { + if (!this.loadConfig()) { + return; + } + + this.geyser = GeyserImpl.load(PlatformType.VIAPROXY, this); + LoopbackUtil.checkAndApplyLoopback(this.logger); + } + + @Override + public void onGeyserEnable() { + if (GeyserImpl.getInstance().isReloading()) { + if (!this.loadConfig()) { + return; + } + } + + this.commandManager = new GeyserCommandManager(this.geyser); + this.commandManager.init(); + + GeyserImpl.start(); + + if (Options.PROTOCOL_VERSION != null && Options.PROTOCOL_VERSION.newerThanOrEqualTo(LegacyProtocolVersion.b1_8tob1_8_1)) { + // Only initialize the ping passthrough if the protocol version is above beta 1.7.3, as that's when the status protocol was added + this.pingPassthrough = GeyserLegacyPingPassthrough.init(this.geyser); + } + } + + @Override + public void onGeyserDisable() { + this.geyser.disable(); + } + + @Override + public void onGeyserShutdown() { + this.geyser.shutdown(); + } + + @Override + public GeyserConfiguration getGeyserConfig() { + return this.config; + } + + @Override + public GeyserLogger getGeyserLogger() { + return this.logger; + } + + @Override + public GeyserCommandManager getGeyserCommandManager() { + return this.commandManager; + } + + @Override + public IGeyserPingPassthrough getGeyserPingPassthrough() { + return this.pingPassthrough; + } + + @Override + public Path getConfigFolder() { + return ROOT_FOLDER.toPath(); + } + + @Override + public BootstrapDumpInfo getDumpInfo() { + return new GeyserViaProxyDumpInfo(); + } + + @NotNull + @Override + public String getServerBindAddress() { + if (Options.BIND_ADDRESS instanceof InetSocketAddress socketAddress) { + return socketAddress.getHostString(); + } else { + throw new IllegalStateException("Unsupported bind address type: " + Options.BIND_ADDRESS.getClass().getName()); + } + } + + @Override + public int getServerPort() { + if (Options.BIND_ADDRESS instanceof InetSocketAddress socketAddress) { + return socketAddress.getPort(); + } else { + throw new IllegalStateException("Unsupported bind address type: " + Options.BIND_ADDRESS.getClass().getName()); + } + } + + @Override + public boolean testFloodgatePluginPresent() { + return false; + } + + private boolean loadConfig() { + try { + final File configFile = FileUtils.fileOrCopiedFromResource(new File(ROOT_FOLDER, "config.yml"), "config.yml", s -> s.replaceAll("generateduuid", UUID.randomUUID().toString()), this); + this.config = FileUtils.loadConfig(configFile, GeyserViaProxyConfiguration.class); + } catch (IOException e) { + this.logger.severe(GeyserLocale.getLocaleStringLog("geyser.config.failed"), e); + return false; + } + this.config.getRemote().setAuthType(Files.isRegularFile(this.config.getFloodgateKeyPath()) ? AuthType.FLOODGATE : AuthType.OFFLINE); + this.logger.setDebug(this.config.isDebugMode()); + GeyserConfiguration.checkGeyserConfiguration(this.config, this.logger); + return true; + } + +} diff --git a/bootstrap/viaproxy/src/main/resources/viaproxy.yml b/bootstrap/viaproxy/src/main/resources/viaproxy.yml new file mode 100644 index 000000000..f42cda77b --- /dev/null +++ b/bootstrap/viaproxy/src/main/resources/viaproxy.yml @@ -0,0 +1,5 @@ +name: "${name}-ViaProxy" +version: "${version}" +author: "${author}" +main: "org.geysermc.geyser.platform.viaproxy.GeyserViaProxyPlugin" +min-version: "3.2.0" diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 89a8d6688..36ca679eb 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -4,14 +4,17 @@ plugins { repositories { gradlePluginPortal() + maven("https://repo.opencollab.dev/maven-snapshots") + maven("https://maven.fabricmc.net") + maven("https://maven.neoforged.net/releases") + maven("https://maven.architectury.dev") } dependencies { - implementation("net.kyori", "indra-common", "3.1.3") - implementation("com.github.johnrengelman", "shadow", "8.1.1") - - // Within the gradle plugin classpath, there is a version conflict between loom and some other - // plugin for databind. This fixes it: minimum 2.13.2 is required by loom. - implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") + implementation(libs.indra) + implementation(libs.shadow) + implementation(libs.architectury.plugin) + implementation(libs.architectury.loom) + implementation(libs.minotaur) } diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 000000000..63bde189b --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,11 @@ +@file:Suppress("UnstableApiUsage") + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" \ No newline at end of file diff --git a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts index 01c769733..3d41a3dd6 100644 --- a/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.base-conventions.gradle.kts @@ -22,8 +22,8 @@ indra { tasks { processResources { - // Spigot, BungeeCord, Velocity, Fabric - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json")) { + // Spigot, BungeeCord, Velocity, Fabric, ViaProxy, NeoForge + filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml", "META-INF/mods.toml")) { expand( "id" to "geyser", "name" to "Geyser", diff --git a/bootstrap/fabric/build.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts similarity index 70% rename from bootstrap/fabric/build.gradle.kts rename to build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index 66af130b3..0842eae84 100644 --- a/bootstrap/fabric/build.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -1,50 +1,22 @@ +@file:Suppress("UnstableApiUsage") + import net.fabricmc.loom.task.RemapJarTask +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.maven plugins { - id("fabric-loom") version "1.0-SNAPSHOT" - id("com.modrinth.minotaur") version "2.+" + id("geyser.publish-conventions") + id("architectury-plugin") + id("dev.architectury.loom") + id("com.modrinth.minotaur") } -dependencies { - //to change the versions see the gradle.properties file - minecraft(libs.fabric.minecraft) - mappings(loom.officialMojangMappings()) - modImplementation(libs.fabric.loader) - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation(libs.fabric.api) - - // This should be in the libs TOML, but something about modImplementation AND include just doesn't work - include(modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")) - - // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. - // You may need to force-disable transitiveness on them. - - api(projects.core) - shadow(projects.core) { - exclude(group = "com.google.guava", module = "guava") - exclude(group = "com.google.code.gson", module = "gson") - exclude(group = "org.slf4j") - exclude(group = "com.nukkitx.fastutil") - exclude(group = "io.netty.incubator") - } +architectury { + minecraft = "1.20.4" } loom { - mixin.defaultRefmapName.set("geyser-fabric-refmap.json") -} - -repositories { - mavenLocal() - 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/") -} - -application { - mainClass.set("org.geysermc.geyser.platform.fabric.GeyserFabricMain") + silentMojangMappingsLicense() } tasks { @@ -59,7 +31,7 @@ tasks { shadowJar { // Mirrors the example fabric project, otherwise tons of dependencies are shaded that shouldn't be configurations = listOf(project.configurations.shadow.get()) - // The remapped shadowJar is the final desired Geyser-Fabric.jar + // The remapped shadowJar is the final desired mod jar archiveVersion.set(project.version.toString()) archiveClassifier.set("shaded") @@ -89,20 +61,32 @@ tasks { remapJar { dependsOn(shadowJar) inputFile.set(shadowJar.get().archiveFile) - archiveBaseName.set("Geyser-Fabric") - archiveVersion.set("") archiveClassifier.set("") + archiveVersion.set("") } register("remapModrinthJar", RemapJarTask::class) { dependsOn(shadowJar) inputFile.set(shadowJar.get().archiveFile) - archiveBaseName.set("geyser-fabric") archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER")) archiveClassifier.set("") } } +dependencies { + minecraft("com.mojang:minecraft:1.20.4") + 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.neoforged.net/releases") +} + modrinth { token.set(System.getenv("MODRINTH_TOKEN")) // Even though this is the default value, apparently this prevents GitHub Actions caching the token? projectId.set("wKkoqHrH") @@ -114,11 +98,5 @@ modrinth { uploadFile.set(tasks.getByPath("remapModrinthJar")) gameVersions.addAll("1.20.4") - - loaders.add("fabric") failSilently.set(true) - - dependencies { - required.project("fabric-api") - } -} +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 36294eaaf..dfdff2187 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { // Ensure AP works in eclipse (no effect on other IDEs) eclipse id("geyser.build-logic") - id("io.freefair.lombok") version "8.3" apply false + alias(libs.plugins.lombok) apply false } allprojects { @@ -12,18 +12,18 @@ allprojects { description = properties["description"] as String } -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} - -val platforms = setOf( - projects.fabric, +val basePlatforms = setOf( projects.bungeecord, projects.spigot, projects.standalone, projects.velocity, + projects.viaproxy +).map { it.dependencyProject } + +val moddedPlatforms = setOf( + projects.fabric, + projects.neoforge, + projects.mod ).map { it.dependencyProject } subprojects { @@ -34,7 +34,8 @@ subprojects { } when (this) { - in platforms -> plugins.apply("geyser.platform-conventions") + in basePlatforms -> plugins.apply("geyser.platform-conventions") + in moddedPlatforms -> plugins.apply("geyser.modded-conventions") else -> plugins.apply("geyser.base-conventions") } -} +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 7e54cc725..7257b5fd6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,8 +1,7 @@ import net.kyori.blossom.BlossomExtension plugins { - id("net.kyori.blossom") - id("net.kyori.indra.git") + alias(libs.plugins.blossom) id("geyser.publish-conventions") } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 19760049e..53173874e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -44,20 +44,20 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; import org.geysermc.api.Geyser; -import org.geysermc.geyser.api.command.CommandSource; -import org.geysermc.geyser.api.util.MinecraftVersion; -import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; import org.geysermc.erosion.packet.Packets; import org.geysermc.floodgate.core.FloodgatePlatform; import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.command.CommandSource; import org.geysermc.geyser.api.event.EventBus; import org.geysermc.geyser.api.event.EventRegistrar; import org.geysermc.geyser.api.event.lifecycle.*; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.network.BedrockListener; import org.geysermc.geyser.api.network.RemoteServer; +import org.geysermc.geyser.api.util.MinecraftVersion; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; @@ -162,12 +162,17 @@ public class GeyserImpl implements GeyserApi { @Getter private static GeyserImpl instance; - -/** + /** * Determines if we're currently reloading. Replaces per-bootstrap reload checks */ private volatile boolean isReloading; + /** + * Determines if Geyser is currently enabled. This is used to determine if {@link #disable()} should be called during {@link #shutdown()}. + */ + @Setter + private boolean isEnabled; + private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap, FloodgatePlatform floodgatePlatform) { instance = this; @@ -227,6 +232,7 @@ public class GeyserImpl implements GeyserApi { if (ex != null) { return; } + MinecraftLocale.ensureEN_US(); String locale = GeyserLocale.getDefaultLocale(); if (!"en_us".equals(locale)) { @@ -356,15 +362,17 @@ public class GeyserImpl implements GeyserApi { logger.info("Broadcast port set from system property: " + parsedPort); } - boolean floodgatePresent = bootstrap.testFloodgatePluginPresent() || floodgateProvider != null; //todo - if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { - logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " - + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); - return; - } else if (config.isAutoconfiguredRemote() && floodgatePresent) { - // Floodgate installed means that the user wants Floodgate authentication - logger.debug("Auto-setting to Floodgate authentication."); - config.getRemote().setAuthType(AuthType.FLOODGATE); + if (platformType != PlatformType.VIAPROXY) { + boolean floodgatePresent = bootstrap.testFloodgatePluginPresent() || floodgateProvider != null; //todo + if (config.getRemote().authType() == AuthType.FLOODGATE && !floodgatePresent) { + logger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); + return; + } else if (config.isAutoconfiguredRemote() && floodgatePresent) { + // Floodgate installed means that the user wants Floodgate authentication + logger.debug("Auto-setting to Floodgate authentication."); + config.getRemote().setAuthType(AuthType.FLOODGATE); + } } } //TODO end @@ -644,12 +652,14 @@ public class GeyserImpl implements GeyserApi { Registries.RESOURCE_PACKS.get().clear(); - bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); + this.setEnabled(false); } public void shutdown() { shuttingDown = true; - this.disable(); + if (isEnabled) { + this.disable(); + } this.commandManager().getCommands().clear(); // Disable extensions, fire the shutdown event @@ -782,6 +792,7 @@ public class GeyserImpl implements GeyserApi { } else { instance.initialize(); } + instance.setEnabled(true); } public GeyserLogger getLogger() { diff --git a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java index e7a9f4f1e..f6c5140d8 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java +++ b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java @@ -187,7 +187,7 @@ public class SkullResourcePackManager { ZipEntry entry = new ZipEntry("skull_resource_pack/pack_icon.png"); zipOS.putNextEntry(entry); - zipOS.write(FileUtils.readAllBytes("icon.png")); + zipOS.write(FileUtils.readAllBytes("assets/geyser/icon.png")); zipOS.closeEntry(); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java index 132de67cb..0651039a0 100644 --- a/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java +++ b/core/src/main/java/org/geysermc/geyser/session/PendingMicrosoftAuthentication.java @@ -123,7 +123,8 @@ public class PendingMicrosoftAuthentication { public CompletableFuture getCode(boolean offlineAccess) { // Request the code - CompletableFuture code = CompletableFuture.supplyAsync(() -> tryGetCode(offlineAccess)); + CompletableFuture code = CompletableFuture.supplyAsync( + () -> tryGetCode(offlineAccess)); // Once the code is received, continuously try to request the access token, profile, etc code.thenRun(() -> performLoginAttempt(System.currentTimeMillis())); return code; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java index daf42a68e..c0be2c624 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginDisconnectTranslator.java @@ -51,7 +51,7 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator arrayOf("base", "isolated").forEach {