If it's a dedicated server, it will return the server port specified in the {@code server.properties} file.
+ *
If it's an integrated server, it will return the LAN port if opened, else -1.
+ *
+ *
+ * The reason is that {@link MinecraftServer#getServerPort()} doesn't return the LAN port if it's the integrated server,
+ * and changing the behavior of this method via a mixin should be avoided as it could have unexpected consequences.
+ *
+ * @return The server port.
+ */
+ int geyser$getServerPort();
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java
new file mode 100644
index 000000000..da753c44f
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/ModInfo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric;
+
+import net.fabricmc.loader.api.ModContainer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A wrapper for Fabric mod information to be presented in a Geyser dump
+ */
+public class ModInfo {
+
+ private final String name;
+ private final String id;
+ private final String version;
+ private final List authors;
+
+ public ModInfo(ModContainer mod) {
+ this.name = mod.getMetadata().getName();
+ this.id = mod.getMetadata().getId();
+ this.authors = new ArrayList<>();
+ mod.getMetadata().getAuthors().forEach((person) -> this.authors.add(person.getName()));
+ this.version = mod.getMetadata().getVersion().getFriendlyString();
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public List getAuthors() {
+ return this.authors;
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java
new file mode 100644
index 000000000..20dee1b21
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/FabricCommandSender.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric.command;
+
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.text.Text;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.text.ChatColor;
+import org.geysermc.platform.fabric.GeyserFabricMod;
+
+public class FabricCommandSender implements GeyserCommandSource {
+
+ private final ServerCommandSource source;
+
+ public FabricCommandSender(ServerCommandSource source) {
+ this.source = source;
+ }
+
+ @Override
+ public String name() {
+ return source.getName();
+ }
+
+ @Override
+ public void sendMessage(String message) {
+ if (source.getEntity() instanceof ServerPlayerEntity) {
+ ((ServerPlayerEntity) source.getEntity()).sendMessage(Text.literal(message), false);
+ } else {
+ GeyserImpl.getInstance().getLogger().info(ChatColor.toANSI(message + ChatColor.RESET));
+ }
+ }
+
+ @Override
+ public boolean isConsole() {
+ return !(source.getEntity() instanceof ServerPlayerEntity);
+ }
+
+ @Override
+ public boolean hasPermission(String s) {
+ // Mostly copied from fabric's world manager since the method there takes a GeyserSession
+
+ // Workaround for our commands because fabric doesn't have native permissions
+ for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
+ if (executor.getCommand().permission().equals(s)) {
+ return executor.canRun(source);
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java
new file mode 100644
index 000000000..07b8bd519
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandExecutor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric.command;
+
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.context.CommandContext;
+import net.minecraft.server.command.ServerCommandSource;
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.command.GeyserCommand;
+import org.geysermc.geyser.command.GeyserCommandExecutor;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.text.ChatColor;
+import org.geysermc.geyser.text.GeyserLocale;
+import org.geysermc.platform.fabric.GeyserFabricMod;
+import org.geysermc.platform.fabric.GeyserFabricPermissions;
+
+import java.util.Collections;
+
+public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implements Command {
+
+ private final GeyserCommand command;
+ /**
+ * Whether the command requires an OP permission level of 2 or greater
+ */
+ private final boolean requiresPermission;
+
+ public GeyserFabricCommandExecutor(GeyserImpl connector, GeyserCommand command, boolean requiresPermission) {
+ super(connector, Collections.singletonMap(command.name(), command));
+ this.command = command;
+ this.requiresPermission = requiresPermission;
+ }
+
+ /**
+ * Determine whether or not a command source is allowed to run a given executor.
+ *
+ * @param source The command source attempting to run the command
+ * @return True if the command source is allowed to
+ */
+ public boolean canRun(ServerCommandSource source) {
+ return !requiresPermission() || source.hasPermissionLevel(GeyserFabricPermissions.RESTRICTED_MIN_LEVEL);
+ }
+
+ @Override
+ public int run(CommandContext context) {
+ ServerCommandSource source = (ServerCommandSource) context.getSource();
+ FabricCommandSender sender = new FabricCommandSender(source);
+ GeyserSession session = getGeyserSession(sender);
+ if (!canRun(source)) {
+ sender.sendMessage(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail"));
+ return 0;
+ }
+ if (this.command.name().equals("reload")) {
+ GeyserFabricMod.getInstance().setReloading(true);
+ }
+
+ if (command.isBedrockOnly() && session == null) {
+ sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
+ return 0;
+ }
+ command.execute(session, sender, new String[0]);
+ return 0;
+ }
+
+ public GeyserCommand getCommand() {
+ return command;
+ }
+
+ /**
+ * Returns whether the command requires permission level of {@link GeyserFabricPermissions#RESTRICTED_MIN_LEVEL} or higher to be ran
+ */
+ public boolean requiresPermission() {
+ return requiresPermission;
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java
new file mode 100644
index 000000000..feaf40130
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/command/GeyserFabricCommandManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric.command;
+
+import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.command.GeyserCommandManager;
+
+public class GeyserFabricCommandManager extends GeyserCommandManager {
+
+ public GeyserFabricCommandManager(GeyserImpl connector) {
+ super(connector);
+ }
+
+ @Override
+ public String description(String command) {
+ return "";
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java
new file mode 100644
index 000000000..6a6d3e0e6
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/client/IntegratedServerMixin.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric.mixin.client;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.integrated.IntegratedServer;
+import net.minecraft.text.Text;
+import net.minecraft.world.GameMode;
+import org.geysermc.geyser.text.GeyserLocale;
+import org.geysermc.platform.fabric.GeyserFabricMod;
+import org.geysermc.platform.fabric.GeyserServerPortGetter;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Environment(EnvType.CLIENT)
+@Mixin(IntegratedServer.class)
+public class IntegratedServerMixin implements GeyserServerPortGetter {
+ @Shadow
+ private int lanPort;
+
+ @Shadow @Final private MinecraftClient client;
+
+ @Inject(method = "openToLan", at = @At("RETURN"))
+ private void onOpenToLan(GameMode gameMode, boolean cheatsAllowed, int port, CallbackInfoReturnable cir) {
+ if (cir.getReturnValueZ()) {
+ // If the LAN is opened, starts Geyser.
+ GeyserFabricMod.getInstance().startGeyser((MinecraftServer) (Object) this);
+ // Ensure player locale has been loaded, in case it's different from Java system language
+ GeyserLocale.loadGeyserLocale(this.client.options.language);
+ // Give indication that Geyser is loaded
+ this.client.player.sendMessage(Text.literal(GeyserLocale.getPlayerLocaleString("geyser.core.start",
+ this.client.options.language, "localhost", String.valueOf(this.lanPort))), false);
+ }
+ }
+
+ @Override
+ public int geyser$getServerPort() {
+ return this.lanPort;
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java
new file mode 100644
index 000000000..a41a08342
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/mixin/server/MinecraftDedicatedServerMixin.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 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.platform.fabric.mixin.server;
+
+import com.mojang.datafixers.DataFixer;
+import net.minecraft.resource.ResourcePackManager;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.SaveLoader;
+import net.minecraft.server.WorldGenerationProgressListenerFactory;
+import net.minecraft.server.dedicated.MinecraftDedicatedServer;
+import net.minecraft.util.ApiServices;
+import net.minecraft.world.level.storage.LevelStorage;
+import org.geysermc.platform.fabric.GeyserServerPortGetter;
+import org.spongepowered.asm.mixin.Mixin;
+
+import java.net.Proxy;
+
+@Mixin(MinecraftDedicatedServer.class)
+public abstract class MinecraftDedicatedServerMixin extends MinecraftServer implements GeyserServerPortGetter {
+ public MinecraftDedicatedServerMixin(Thread serverThread, LevelStorage.Session session, ResourcePackManager dataPackManager, SaveLoader saveLoader, Proxy proxy, DataFixer dataFixer, ApiServices apiServices, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory) {
+ super(serverThread, session, dataPackManager, saveLoader, proxy, dataFixer, apiServices, worldGenerationProgressListenerFactory);
+ }
+
+ @Override
+ public int geyser$getServerPort() {
+ return this.getServerPort();
+ }
+}
diff --git a/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java
new file mode 100644
index 000000000..40c7fd302
--- /dev/null
+++ b/bootstrap/fabric/src/main/java/org/geysermc/platform/fabric/world/GeyserFabricWorldManager.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 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.platform.fabric.world;
+
+import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
+import com.nukkitx.nbt.NbtMapBuilder;
+import com.nukkitx.nbt.NbtType;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.block.entity.LecternBlockEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.WritableBookItem;
+import net.minecraft.item.WrittenBookItem;
+import net.minecraft.nbt.NbtList;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.math.BlockPos;
+import org.geysermc.geyser.level.GeyserWorldManager;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
+import org.geysermc.geyser.util.BlockEntityUtils;
+import org.geysermc.platform.fabric.GeyserFabricMod;
+import org.geysermc.platform.fabric.command.GeyserFabricCommandExecutor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class GeyserFabricWorldManager extends GeyserWorldManager {
+ private final MinecraftServer server;
+
+ public GeyserFabricWorldManager(MinecraftServer server) {
+ this.server = server;
+ }
+
+ @Override
+ public boolean shouldExpectLecternHandled() {
+ return true;
+ }
+
+ @Override
+ public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
+ Runnable lecternGet = () -> {
+ // Mostly a reimplementation of Spigot lectern support
+ PlayerEntity player = getPlayer(session);
+ if (player != null) {
+ BlockEntity blockEntity = player.world.getBlockEntity(new BlockPos(x, y, z));
+ if (!(blockEntity instanceof LecternBlockEntity lectern)) {
+ return;
+ }
+
+ if (!lectern.hasBook()) {
+ if (!isChunkLoad) {
+ BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
+ }
+ return;
+ }
+
+ ItemStack book = lectern.getBook();
+ int pageCount = WrittenBookItem.getPageCount(book);
+ boolean hasBookPages = pageCount > 0;
+ NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? pageCount : 1);
+ lecternTag.putInt("page", lectern.getCurrentPage() / 2);
+ NbtMapBuilder bookTag = NbtMap.builder()
+ .putByte("Count", (byte) book.getCount())
+ .putShort("Damage", (short) 0)
+ .putString("Name", "minecraft:writable_book");
+ List pages = new ArrayList<>(hasBookPages ? pageCount : 1);
+ if (hasBookPages && WritableBookItem.isValid(book.getNbt())) {
+ NbtList listTag = book.getNbt().getList("pages", 8);
+
+ for (int i = 0; i < listTag.size(); i++) {
+ String page = listTag.getString(i);
+ NbtMapBuilder pageBuilder = NbtMap.builder()
+ .putString("photoname", "")
+ .putString("text", page);
+ pages.add(pageBuilder.build());
+ }
+ } else {
+ // Empty page
+ NbtMapBuilder pageBuilder = NbtMap.builder()
+ .putString("photoname", "")
+ .putString("text", "");
+ pages.add(pageBuilder.build());
+ }
+
+ bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
+ lecternTag.putCompound("book", bookTag.build());
+ NbtMap blockEntityTag = lecternTag.build();
+ BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
+ }
+ };
+ if (isChunkLoad) {
+ // Hacky hacks to allow lectern loading to be delayed
+ session.scheduleInEventLoop(() -> server.execute(lecternGet), 1, TimeUnit.SECONDS);
+ } else {
+ server.execute(lecternGet);
+ }
+ return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build();
+ }
+
+ @Override
+ public boolean hasPermission(GeyserSession session, String permission) {
+ // Workaround for our commands because fabric doesn't have native permissions
+ for (GeyserFabricCommandExecutor executor : GeyserFabricMod.getInstance().getCommandExecutors()) {
+ if (executor.getCommand().permission().equals(permission)) {
+ return executor.canRun(getPlayer(session).getCommandSource());
+ }
+ }
+
+ return false;
+ }
+
+ private PlayerEntity getPlayer(GeyserSession session) {
+ return server.getPlayerManager().getPlayer(session.getPlayerEntity().getUuid());
+ }
+}
diff --git a/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png b/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png
new file mode 100644
index 000000000..4e6a38a78
Binary files /dev/null and b/bootstrap/fabric/src/main/resources/assets/geyser-fabric/icon.png differ
diff --git a/bootstrap/fabric/src/main/resources/fabric.mod.json b/bootstrap/fabric/src/main/resources/fabric.mod.json
new file mode 100644
index 000000000..9d66ca08c
--- /dev/null
+++ b/bootstrap/fabric/src/main/resources/fabric.mod.json
@@ -0,0 +1,30 @@
+{
+ "schemaVersion": 1,
+ "id": "geyser-fabric",
+ "version": "${version}",
+ "name": "Geyser-Fabric",
+ "description": "A bridge/proxy allowing you to connect to Minecraft: Java Edition servers with Minecraft: Bedrock Edition. ",
+ "authors": [
+ "GeyserMC"
+ ],
+ "contact": {
+ "website": "https://geysermc.org",
+ "repo": "https://github.com/GeyserMC/Geyser-Fabric"
+ },
+ "license": "MIT",
+ "icon": "assets/geyser-fabric/icon.png",
+ "environment": "*",
+ "entrypoints": {
+ "main": [
+ "org.geysermc.platform.fabric.GeyserFabricMod"
+ ]
+ },
+ "mixins": [
+ "geyser-fabric.mixins.json"
+ ],
+ "depends": {
+ "fabricloader": ">=0.14.8",
+ "fabric": "*",
+ "minecraft": ">=1.19"
+ }
+}
diff --git a/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json
new file mode 100644
index 000000000..6081bee93
--- /dev/null
+++ b/bootstrap/fabric/src/main/resources/geyser-fabric.mixins.json
@@ -0,0 +1,14 @@
+{
+ "required": true,
+ "package": "org.geysermc.platform.fabric.mixin",
+ "compatibilityLevel": "JAVA_16",
+ "client": [
+ "client.IntegratedServerMixin"
+ ],
+ "server": [
+ "server.MinecraftDedicatedServerMixin"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/bootstrap/fabric/src/main/resources/permissions.yml b/bootstrap/fabric/src/main/resources/permissions.yml
new file mode 100644
index 000000000..ae20447ed
--- /dev/null
+++ b/bootstrap/fabric/src/main/resources/permissions.yml
@@ -0,0 +1,13 @@
+# Uncomment any commands that you wish to be run by clients
+# Commented commands require an OP permission of 2 or greater
+commands:
+ - help
+ - advancements
+ - statistics
+ - settings
+ - offhand
+ - tooltips
+# - list
+# - reload
+# - version
+# - dump