diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 827136b4a..cd579c931 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 @@ -118,7 +125,7 @@ jobs: 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: @@ -126,7 +133,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 f2c8f5d8d..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 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 1abdc0230..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,6 +34,7 @@ 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 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 756063af7..db966ec1a 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); + this.geyser = GeyserImpl.load(this.platform.platformType(), this); + + // 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 c3e2e10e8..b82d8cc94 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/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index c0c683920..b7aab2bf0 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://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.1") - implementation("com.github.johnrengelman", "shadow", "7.1.3-SNAPSHOT") - - // 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 c9f984596..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, ViaProxy - filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json", "viaproxy.yml")) { + // 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", @@ -34,4 +34,4 @@ tasks { ) } } -} +} \ No newline at end of file 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 1d434e599..dfdff2187 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,9 @@ plugins { `java-library` // Ensure AP works in eclipse (no effect on other IDEs) - `eclipse` + eclipse id("geyser.build-logic") - id("io.freefair.lombok") version "6.3.0" apply false + alias(libs.plugins.lombok) apply false } allprojects { @@ -12,14 +12,7 @@ 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, @@ -27,6 +20,12 @@ val platforms = setOf( projects.viaproxy ).map { it.dependencyProject } +val moddedPlatforms = setOf( + projects.fabric, + projects.neoforge, + projects.mod +).map { it.dependencyProject } + subprojects { apply { plugin("java-library") @@ -35,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 f67b652be..6d289ae37 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 6a8efaba1..8b54f5298 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -221,6 +221,7 @@ public class GeyserImpl implements GeyserApi { if (ex != null) { return; } + MinecraftLocale.ensureEN_US(); String locale = GeyserLocale.getDefaultLocale(); if (!"en_us".equals(locale)) { 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/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png b/core/src/main/resources/assets/geyser/icon.png similarity index 100% rename from bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png rename to core/src/main/resources/assets/geyser/icon.png diff --git a/core/src/main/resources/icon.png b/core/src/main/resources/icon.png deleted file mode 100644 index 4e6a38a78..000000000 Binary files a/core/src/main/resources/icon.png and /dev/null differ diff --git a/gradle.properties b/gradle.properties index 74504759e..a8e5eaaaf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # Gradle settings org.gradle.jvmargs=-Xmx4G -org.gradle.configureondemand=true -org.gradle.caching=true +org.gradle.daemon=false org.gradle.parallel=true +org.gradle.caching=true org.gradle.vfs.watch=false group=org.geysermc diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0dc0fbc4a..4ce5dda1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,18 @@ viaproxy = "3.2.0-SNAPSHOT" fabric-minecraft = "1.20.4" fabric-loader = "0.15.2" fabric-api = "0.91.2+1.20.4" +fabric-permissions = "0.2-SNAPSHOT" +neoforge-minecraft = "20.4.48-beta" +mixin = "0.8.5" + +# plugin versions +indra = "3.1.3" +shadow = "7.1.3-SNAPSHOT" +architectury-plugin = "3.4-SNAPSHOT" +architectury-loom = "1.4-SNAPSHOT" +minotaur = "2.8.7" +lombok = "8.4" +blossom = "1.2.0" [libraries] base-api = { group = "org.geysermc.api", name = "base-api", version.ref = "base-api" } @@ -75,10 +87,15 @@ jline-reader = { group = "org.jline", name = "jline-reader", version.ref = "jlin folia-api = { group = "dev.folia", name = "folia-api", version.ref = "folia" } paper-mojangapi = { group = "io.papermc.paper", name = "paper-mojangapi", version.ref = "folia" } -# check these on https://modmuss50.me/fabric.html +mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } + +# Check these on https://modmuss50.me/fabric.html fabric-minecraft = { group = "com.mojang", name = "minecraft", version.ref = "fabric-minecraft" } fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } +fabric-permissions = { group = "me.lucko", name = "fabric-permissions-api", version.ref = "fabric-permissions" } + +neoforge-minecraft = { group = "net.neoforged", name = "neoforge", version.ref = "neoforge-minecraft" } adapters-spigot = { group = "org.geysermc.geyser.adapters", name = "spigot-all", version.ref = "adapters" } bungeecord-proxy = { group = "com.github.SpigotMC.BungeeCord", name = "bungeecord-proxy", version.ref = "bungeecord" } @@ -104,6 +121,18 @@ math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" } blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version.ref = "blockstateupdater"} +# plugins +indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" } +shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" } +architectury-plugin = { group = "architectury-plugin", name = "architectury-plugin.gradle.plugin", version.ref = "architectury-plugin" } +architectury-loom = { group = "dev.architectury.loom", name = "dev.architectury.loom.gradle.plugin", version.ref = "architectury-loom" } +minotaur = { group = "com.modrinth.minotaur", name = "Minotaur", version.ref = "minotaur" } + +[plugins] +lombok = { id = "io.freefair.lombok", version.ref = "lombok" } +indra = { id = "net.kyori.indra", version.ref = "indra" } +blossom = { id = "net.kyori.blossom", version.ref = "blossom" } + [bundles] jackson = [ "jackson-annotations", "jackson-core", "jackson-dataformat-yaml" ] fastutil = [ "fastutil-int-int-maps", "fastutil-int-long-maps", "fastutil-int-byte-maps", "fastutil-int-boolean-maps", "fastutil-object-int-maps", "fastutil-object-object-maps" ] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee..db9a6b825 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 6c644bf09..68848bca4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,8 @@ +@file:Suppress("UnstableApiUsage") + enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { -// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { // Floodgate, Cumulus etc. maven("https://repo.opencollab.dev/main") @@ -18,6 +19,11 @@ dependencyResolutionManagement { mavenContent { snapshotsOnly() } } + // NeoForge + maven("https://maven.neoforged.net/releases") { + mavenContent { releasesOnly() } + } + // Minecraft maven("https://libraries.minecraft.net") { name = "minecraft" @@ -44,13 +50,11 @@ dependencyResolutionManagement { pluginManagement { repositories { gradlePluginPortal() + + maven("https://repo.opencollab.dev/maven-snapshots/") maven("https://maven.fabricmc.net/") - maven("https://repo.opencollab.dev/maven-snapshots") - } - plugins { - id("net.kyori.blossom") version "1.2.0" - id("net.kyori.indra") - id("net.kyori.indra.git") + maven("https://maven.architectury.dev/") + maven("https://maven.neoforged.net/releases") } includeBuild("build-logic") } @@ -61,6 +65,8 @@ include(":ap") include(":api") include(":bungeecord") include(":fabric") +include(":neoforge") +include(":mod") include(":spigot") include(":standalone") include(":velocity") @@ -70,7 +76,9 @@ include(":core") // Specify project dirs project(":bungeecord").projectDir = file("bootstrap/bungeecord") -project(":fabric").projectDir = file("bootstrap/fabric") +project(":fabric").projectDir = file("bootstrap/mod/fabric") +project(":neoforge").projectDir = file("bootstrap/mod/neoforge") +project(":mod").projectDir = file("bootstrap/mod") project(":spigot").projectDir = file("bootstrap/spigot") project(":standalone").projectDir = file("bootstrap/standalone") project(":velocity").projectDir = file("bootstrap/velocity")