diff --git a/README.md b/README.md
index aa04f9d..e1a660d 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
Allows the connection to/from different Minecraft versions on your Minecraft client/server (LAN worlds too)
-This mod can be installed on 1.8.9, 1.12.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, 1.18.2, 1.19.4, 1.20.1, 1.20.4, 1.20.6 with Fabric Loader.
+This mod can be installed on 1.8.9, 1.12.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, 1.18.2, 1.19.4, 1.20.1, 1.20.4, 1.20.6, 1.21 with Fabric Loader.
## Dependencies
diff --git a/gradle.properties b/gradle.properties
index 978d79b..a96cadc 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,10 +2,10 @@
org.gradle.jvmargs=-Xms32M -Xmx4G -XX:+UseG1GC -XX:+UseStringDeduplication
loader_version=0.15.11
-viaver_version=5.0.0-SNAPSHOT
+viaver_version=5.0.0
yaml_version=2.2
-publish_mc_versions=1.20.6, 1.20.4, 1.20.1, 1.19.4, 1.18.2, 1.17.1, 1.16.5, 1.15.2, 1.14.4, 1.12.2, 1.8.9
+publish_mc_versions=1.21, 1.20.6, 1.20.4, 1.20.1, 1.19.4, 1.18.2, 1.17.1, 1.16.5, 1.15.2, 1.14.4, 1.12.2, 1.8.9
# example: 1.19.1-rc1. Can be a blank value
modrinth_mc_snapshot=
# example: 1.19-Snapshot. Can be a blank value
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 063eb77..f847891 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -19,6 +19,7 @@ include("viafabric-mc1194")
include("viafabric-mc1201")
include("viafabric-mc1204")
include("viafabric-mc1206")
+include("viafabric-mc121")
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 7fe6860..49aca38 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -13,8 +13,8 @@
},
"depends": {
"fabricloader": ">=0.14.0",
- "minecraft": ["1.8.9", "1.12.2", "1.14.4", "1.15.2", "1.16.5", "1.17.1", "1.18.2", "1.19.4", "1.20.1", "1.20.3", "1.20.4", "1.20.5", "1.20.6"],
- "viaversion": ">=4.10.0"
+ "minecraft": ["1.8.9", "1.12.2", "1.14.4", "1.15.2", "1.16.5", "1.17.1", "1.18.2", "1.19.4", "1.20.1", "1.20.3", "1.20.4", "1.20.5", "1.20.6", "1.21"],
+ "viaversion": ">=5.0.0"
},
"breaks": {
"viafabricplus": "*"
diff --git a/viafabric-mc121/build.gradle.kts b/viafabric-mc121/build.gradle.kts
new file mode 100644
index 0000000..8fa4e23
--- /dev/null
+++ b/viafabric-mc121/build.gradle.kts
@@ -0,0 +1,11 @@
+dependencies {
+ minecraft("com.mojang:minecraft:1.21")
+ mappings("net.fabricmc:yarn:1.21+build.1:v2")
+
+ modImplementation("net.fabricmc.fabric-api:fabric-api:0.100.1+1.21")
+ modImplementation("com.terraformersmc:modmenu:11.0.0-beta.1")
+}
+
+tasks.compileJava {
+ options.release.set(21)
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabric.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabric.java
new file mode 100644
index 0000000..716f192
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabric.java
@@ -0,0 +1,119 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.builder.RequiredArgumentBuilder;
+import com.viaversion.fabric.common.config.VFConfig;
+import com.viaversion.fabric.common.platform.FabricInjector;
+import com.viaversion.fabric.common.protocol.HostnameParserProtocol;
+import com.viaversion.fabric.common.util.JLoggerToLog4j;
+import com.viaversion.fabric.mc121.commands.VFCommandHandler;
+import com.viaversion.fabric.mc121.platform.FabricPlatform;
+import com.viaversion.fabric.mc121.platform.VFLoader;
+import com.viaversion.viaversion.ViaManagerImpl;
+import com.viaversion.viaversion.api.Via;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import io.netty.channel.DefaultEventLoop;
+import io.netty.channel.EventLoop;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.command.CommandSource;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.logging.Logger;
+
+public class ViaFabric implements ModInitializer {
+ public static final Logger JLOGGER = new JLoggerToLog4j(LogManager.getLogger("ViaFabric"));
+ public static final ExecutorService ASYNC_EXECUTOR;
+ public static final EventLoop EVENT_LOOP;
+ public static final CompletableFuture INIT_FUTURE = new CompletableFuture<>();
+ public static VFConfig config;
+
+ static {
+ ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("ViaFabric-%d").build();
+ ASYNC_EXECUTOR = Executors.newFixedThreadPool(8, factory);
+ EVENT_LOOP = new DefaultEventLoop(factory);
+ EVENT_LOOP.submit(INIT_FUTURE::join); // https://github.com/ViaVersion/ViaFabric/issues/53 ugly workaround code but works tm
+ }
+
+ public static LiteralArgumentBuilder command(String commandName) {
+ return LiteralArgumentBuilder.literal(commandName)
+ .then(
+ RequiredArgumentBuilder
+ .argument("args", StringArgumentType.greedyString())
+ .executes(((VFCommandHandler) Via.getManager().getCommandHandler())::execute)
+ .suggests(((VFCommandHandler) Via.getManager().getCommandHandler())::suggestion)
+ )
+ .executes(((VFCommandHandler) Via.getManager().getCommandHandler())::execute);
+ }
+
+ @Override
+ public void onInitialize() {
+ FabricPlatform platform = new FabricPlatform();
+
+ Via.init(ViaManagerImpl.builder()
+ .injector(new FabricInjector())
+ .loader(new VFLoader())
+ .commandHandler(new VFCommandHandler())
+ .platform(platform).build());
+
+ platform.init();
+
+ ViaManagerImpl manager = (ViaManagerImpl) Via.getManager();
+ manager.init();
+
+ HostnameParserProtocol.INSTANCE.initialize();
+ HostnameParserProtocol.INSTANCE.register(Via.getManager().getProviders());
+ ProtocolVersion.register(-2, "AUTO");
+
+ FabricLoader.getInstance().getEntrypoints("viafabric:via_api_initialized", Runnable.class).forEach(Runnable::run);
+
+ registerCommandsV1();
+
+ config = new VFConfig(FabricLoader.getInstance().getConfigDir().resolve("ViaFabric")
+ .resolve("viafabric.yml").toFile(), JLOGGER);
+
+ manager.onServerLoaded();
+
+ INIT_FUTURE.complete(null);
+ }
+
+
+ private void registerCommandsV1() {
+ try {
+ CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, env) -> dispatcher.register(command("viaversion")));
+ CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, env) -> dispatcher.register(command("viaver")));
+ CommandRegistrationCallback.EVENT.register((dispatcher, dedicated, env) -> dispatcher.register(command("vvfabric")));
+ if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(command("viafabricclient")));
+ }
+ } catch (NoClassDefFoundError ignored) {
+ JLOGGER.info("Couldn't register command as Fabric Commands V1 isn't installed");
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabricClient.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabricClient.java
new file mode 100644
index 0000000..83bd42f
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/ViaFabricClient.java
@@ -0,0 +1,54 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121;
+
+import com.viaversion.fabric.mc121.gui.ViaConfigScreen;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.fabricmc.fabric.api.client.screen.v1.Screens;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.ButtonTextures;
+import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.TexturedButtonWidget;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+public class ViaFabricClient implements ClientModInitializer {
+ @Override
+ public void onInitializeClient() {
+ registerGui();
+ }
+
+ private void registerGui() {
+ try {
+ ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
+ if (!(screen instanceof MultiplayerScreen)) return;
+ ButtonWidget enableClientSideViaVersion = new TexturedButtonWidget(scaledWidth / 2 + 113, 10,
+ 40, 20, // Size
+ new ButtonTextures(Identifier.of("viafabric", "widget_unfocused"), Identifier.of("viafabric", "widget_focused")),
+ it -> MinecraftClient.getInstance().setScreen(new ViaConfigScreen(screen)),
+ Text.translatable("gui.via_button"));
+ if (ViaFabric.config.isHideButton()) enableClientSideViaVersion.visible = false;
+ Screens.getButtons(screen).add(enableClientSideViaVersion);
+ });
+ } catch (NoClassDefFoundError ignored) {
+ ViaFabric.JLOGGER.info("Couldn't register screen handler as Fabric Screen isn't installed");
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/NMSCommandSender.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/NMSCommandSender.java
new file mode 100644
index 0000000..e6c6b0c
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/NMSCommandSender.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.commands;
+
+import com.viaversion.viaversion.api.command.ViaCommandSender;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.minecraft.command.CommandSource;
+import net.minecraft.entity.Entity;
+import net.minecraft.registry.DynamicRegistryManager;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+public class NMSCommandSender implements ViaCommandSender {
+ private final CommandSource source;
+
+ public NMSCommandSender(CommandSource source) {
+ this.source = source;
+ }
+
+ @Override
+ public boolean hasPermission(String s) {
+ // https://gaming.stackexchange.com/questions/138602/what-does-op-permission-level-do
+ return source.hasPermissionLevel(3);
+ }
+
+ public static MutableText fromLegacy(String legacy) {
+ return Text.Serialization.fromJson(legacy, DynamicRegistryManager.EMPTY);
+ }
+
+ @Override
+ public void sendMessage(String s) {
+ if (source instanceof ServerCommandSource) {
+ ((ServerCommandSource) source).sendFeedback(() -> fromLegacy(s), false);
+ } else if (source instanceof FabricClientCommandSource) {
+ ((FabricClientCommandSource) source).sendFeedback(fromLegacy(s));
+ }
+ }
+
+ @Override
+ public UUID getUUID() {
+ if (source instanceof ServerCommandSource) {
+ Entity entity = ((ServerCommandSource) source).getEntity();
+ if (entity != null) return entity.getUuid();
+ } else if (source instanceof FabricClientCommandSource) {
+ return ((FabricClientCommandSource) source).getPlayer().getUuid();
+ }
+ return UUID.nameUUIDFromBytes(getName().getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public String getName() {
+ if (source instanceof ServerCommandSource) {
+ return ((ServerCommandSource) source).getName();
+ } else if (source instanceof FabricClientCommandSource) {
+ return ((FabricClientCommandSource) source).getPlayer().getName().getString();
+ }
+ return "?";
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/VFCommandHandler.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/VFCommandHandler.java
new file mode 100644
index 0000000..0d40b53
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/commands/VFCommandHandler.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.commands;
+
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import com.viaversion.fabric.common.commands.subs.LeakDetectSubCommand;
+import com.viaversion.viaversion.commands.ViaCommandHandler;
+import net.minecraft.command.CommandSource;
+
+import java.util.concurrent.CompletableFuture;
+
+public class VFCommandHandler extends ViaCommandHandler {
+ {
+ try {
+ registerSubCommand(new LeakDetectSubCommand());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int execute(CommandContext extends CommandSource> ctx) {
+ String[] args = new String[0];
+ try {
+ args = StringArgumentType.getString(ctx, "args").split(" ");
+ } catch (IllegalArgumentException ignored) {
+ }
+ onCommand(
+ new NMSCommandSender(ctx.getSource()),
+ args
+ );
+ return 1;
+ }
+
+ public CompletableFuture suggestion(CommandContext extends CommandSource> ctx, SuggestionsBuilder builder) {
+ String[] args;
+ try {
+ args = StringArgumentType.getString(ctx, "args").split(" ", -1);
+ } catch (IllegalArgumentException ignored) {
+ args = new String[]{""};
+ }
+ String[] pref = args.clone();
+ pref[pref.length - 1] = "";
+ String prefix = String.join(" ", pref);
+ onTabComplete(new NMSCommandSender(ctx.getSource()), args)
+ .stream()
+ .map(it -> {
+ SuggestionsBuilder b = new SuggestionsBuilder(builder.getInput(), prefix.length() + builder.getStart());
+ b.suggest(it);
+ return b;
+ })
+ .forEach(builder::add);
+ return builder.buildFuture();
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ModMenuConfig.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ModMenuConfig.java
new file mode 100644
index 0000000..6925797
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ModMenuConfig.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.gui;
+
+import com.google.common.collect.ImmutableMap;
+import com.terraformersmc.modmenu.api.ConfigScreenFactory;
+import com.terraformersmc.modmenu.api.ModMenuApi;
+
+import java.util.Map;
+
+public class ModMenuConfig implements ModMenuApi {
+ @Override
+ public ConfigScreenFactory> getModConfigScreenFactory() {
+ return ViaConfigScreen::new;
+ }
+
+ @Override
+ public Map> getProvidedConfigScreenFactories() {
+ return ImmutableMap.of("viafabric", getModConfigScreenFactory());
+ }
+}
\ No newline at end of file
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ViaConfigScreen.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ViaConfigScreen.java
new file mode 100644
index 0000000..0f22561
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/gui/ViaConfigScreen.java
@@ -0,0 +1,172 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.gui;
+
+import com.viaversion.fabric.common.config.AbstractViaConfigScreen;
+import com.viaversion.fabric.common.util.ProtocolUtils;
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ConfirmScreen;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.screen.ScreenTexts;
+import net.minecraft.text.Text;
+
+import java.util.concurrent.CompletableFuture;
+
+@Environment(EnvType.CLIENT)
+public class ViaConfigScreen extends Screen implements AbstractViaConfigScreen {
+ private static CompletableFuture latestProtocolSave;
+ private final Screen parent;
+ private TextFieldWidget protocolVersion;
+
+ public ViaConfigScreen(Screen parent) {
+ super(Text.translatable(TITLE_TRANSLATE_ID));
+ this.parent = parent;
+ }
+
+ @Override
+ protected void init() {
+ int entries = 0;
+
+ this.addDrawableChild(ButtonWidget
+ .builder(getClientSideText(), this::onClickClientSide)
+ .dimensions(calculatePosX(this.width, entries),
+ calculatePosY(this.height, entries), 150, 20)
+ .build());
+ entries++;
+
+ this.addDrawableChild(ButtonWidget
+ .builder(getHideViaButtonText(), this::onHideViaButton)
+ .dimensions(calculatePosX(this.width, entries),
+ calculatePosY(this.height, entries), 150, 20)
+ .build());
+ entries++;
+
+ protocolVersion = new TextFieldWidget(this.textRenderer,
+ calculatePosX(this.width, entries),
+ calculatePosY(this.height, entries),
+ 150, 20, Text.translatable("gui.protocol_version_field.name"));
+ entries++;
+
+ protocolVersion.setTextPredicate(ProtocolUtils::isStartOfProtocolText);
+ protocolVersion.setChangedListener(this::onChangeVersionField);
+ int clientSideVersion = ViaFabric.config.getClientSideVersion();
+ protocolVersion.setText(ProtocolUtils.getProtocolName(clientSideVersion));
+
+ this.addDrawableChild(protocolVersion);
+
+ this.addDrawableChild(ButtonWidget
+ .builder(ScreenTexts.DONE, (it) -> close())
+ .dimensions(this.width / 2 - 100, this.height - 40, 200, 20)
+ .build());
+ }
+
+ private void onChangeVersionField(String text) {
+ protocolVersion.setSuggestion(null);
+ int newVersion = ViaFabric.config.getClientSideVersion();
+
+ Integer parsed = ProtocolUtils.parseProtocolId(text);
+ boolean validProtocol;
+
+ if (parsed != null) {
+ newVersion = parsed;
+ validProtocol = true;
+ } else {
+ validProtocol = false;
+ String[] suggestions = ProtocolUtils.getProtocolSuggestions(text);
+ if (suggestions.length == 1) {
+ protocolVersion.setSuggestion(suggestions[0].substring(text.length()));
+ }
+ }
+
+ protocolVersion.setEditableColor(getProtocolTextColor(ProtocolVersion.getProtocol(newVersion), validProtocol));
+
+ int finalNewVersion = newVersion;
+ if (latestProtocolSave == null) latestProtocolSave = CompletableFuture.completedFuture(null);
+ ViaFabric.config.setClientSideVersion(finalNewVersion);
+ latestProtocolSave = latestProtocolSave.thenRunAsync(ViaFabric.config::save, ViaFabric.ASYNC_EXECUTOR);
+ }
+
+ private void onClickClientSide(ButtonWidget widget) {
+ if (!ViaFabric.config.isClientSideEnabled()) {
+ MinecraftClient.getInstance().setScreen(new ConfirmScreen(
+ answer -> {
+ if (answer) {
+ ViaFabric.config.setClientSideEnabled(true);
+ ViaFabric.config.setClientSideVersion(-2); // AUTO
+ ViaFabric.config.save();
+ widget.setMessage(getClientSideText());
+ }
+ MinecraftClient.getInstance().setScreen(this);
+ },
+ Text.translatable("gui.enable_client_side.question"),
+ Text.translatable("gui.enable_client_side.warning"),
+ Text.translatable("gui.enable_client_side.enable"),
+ Text.translatable("gui.cancel")
+ ));
+ } else {
+ ViaFabric.config.setClientSideEnabled(false);
+ ViaFabric.config.save();
+ }
+ widget.setMessage(getClientSideText());
+ }
+
+ @Override
+ public void removed() {
+ ViaFabric.config.save();
+ }
+
+ @Override
+ public void close() {
+ this.client.setScreen(this.parent);
+ }
+
+ private Text getClientSideText() {
+ return ViaFabric.config.isClientSideEnabled() ?
+ Text.translatable("gui.client_side.disable")
+ : Text.translatable("gui.client_side.enable");
+ }
+
+ private Text getHideViaButtonText() {
+ return ViaFabric.config.isHideButton() ?
+ Text.translatable("gui.hide_via_button.disable") : Text.translatable("gui.hide_via_button.enable");
+ }
+
+ private void onHideViaButton(ButtonWidget widget) {
+ ViaFabric.config.setHideButton(!ViaFabric.config.isHideButton());
+ ViaFabric.config.save();
+ widget.setMessage(getHideViaButtonText());
+ }
+
+ @Override
+ public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) {
+ super.render(drawContext, mouseX, mouseY, delta);
+ drawContext.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 20, 16777215);
+ }
+
+ @Override
+ public void tick() {
+ super.tick();
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/address/client/MixinAllowedAddressResolver.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/address/client/MixinAllowedAddressResolver.java
new file mode 100644
index 0000000..bd9ac71
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/address/client/MixinAllowedAddressResolver.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.address.client;
+
+import com.viaversion.fabric.common.AddressParser;
+import net.minecraft.client.network.Address;
+import net.minecraft.client.network.AllowedAddressResolver;
+import net.minecraft.client.network.ServerAddress;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Optional;
+
+@Mixin(AllowedAddressResolver.class)
+public abstract class MixinAllowedAddressResolver {
+ @Shadow
+ public abstract Optional resolve(ServerAddress address);
+
+ @Inject(method = "resolve", at = @At(value = "HEAD"), cancellable = true)
+ private void resolveVF(ServerAddress address, CallbackInfoReturnable> cir) {
+ AddressParser viaAddr = new AddressParser().parse(address.getAddress());
+ if (viaAddr.viaSuffix == null) {
+ return;
+ }
+
+ ServerAddress realAddress = new ServerAddress(viaAddr.serverAddress, address.getPort());
+
+ cir.setReturnValue(resolve(realAddress).map(it -> viaFabric$addSuffix(it, viaAddr.getSuffixWithOptions())));
+ }
+
+ @Unique
+ private Address viaFabric$addSuffix(Address it, String viaSuffix) {
+ try {
+ return Address.create(new InetSocketAddress(
+ InetAddress.getByAddress(it.getHostName() + "." + viaSuffix,
+ it.getInetSocketAddress().getAddress().getAddress()), it.getPort()));
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/MixinRegistrySyncManager.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/MixinRegistrySyncManager.java
new file mode 100644
index 0000000..c5cd5d9
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/MixinRegistrySyncManager.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.debug;
+
+import com.viaversion.fabric.mc121.ViaFabric;
+import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerConfigurationNetworkHandler;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(value = RegistrySyncManager.class, remap = false)
+public class MixinRegistrySyncManager {
+
+ @Inject(method = "configureClient", at = @At("HEAD"), cancellable = true)
+ private static void ignoreMissingRegistries(ServerConfigurationNetworkHandler handler, MinecraftServer server, CallbackInfo ci) {
+ if (ViaFabric.config.isIgnoreRegistrySyncErrors()) {
+ ci.cancel();
+ }
+ }
+
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnection.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnection.java
new file mode 100644
index 0000000..bcae086
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnection.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.debug.client;
+
+import io.netty.channel.ChannelHandlerContext;
+import net.minecraft.network.ClientConnection;
+import org.slf4j.Logger;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ClientConnection.class)
+public abstract class MixinClientConnection {
+
+ @Shadow
+ @Final
+ private static Logger LOGGER;
+
+ @Inject(method = "exceptionCaught", at = @At("TAIL"))
+ public void exceptionCaught(ChannelHandlerContext context, Throwable ex, CallbackInfo ci) {
+ LOGGER.error("Packet error", ex);
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnectionAccessor.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnectionAccessor.java
new file mode 100644
index 0000000..cfd4b5d
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinClientConnectionAccessor.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.debug.client;
+
+import io.netty.channel.Channel;
+import net.minecraft.network.ClientConnection;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ClientConnection.class)
+public interface MixinClientConnectionAccessor {
+ @Accessor
+ Channel getChannel();
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinDebugHud.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinDebugHud.java
new file mode 100644
index 0000000..b68dbe8
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinDebugHud.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.debug.client;
+
+import com.viaversion.fabric.common.handler.CommonTransformer;
+import com.viaversion.fabric.common.handler.FabricDecodeHandler;
+import com.viaversion.viaversion.api.Via;
+import com.viaversion.viaversion.api.connection.ProtocolInfo;
+import com.viaversion.viaversion.api.connection.UserConnection;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import io.netty.channel.ChannelHandler;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.hud.DebugHud;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.util.List;
+
+@Mixin(DebugHud.class)
+public class MixinDebugHud {
+ @Inject(at = @At("RETURN"), method = "getLeftText")
+ protected void getLeftText(CallbackInfoReturnable> info) {
+ String line = "[ViaFabric] I: " + Via.getManager().getConnectionManager().getConnections().size() + " (F: "
+ + Via.getManager().getConnectionManager().getConnectedClients().size() + ")";
+ @SuppressWarnings("ConstantConditions") ChannelHandler viaDecoder = ((MixinClientConnectionAccessor) MinecraftClient.getInstance().getNetworkHandler()
+ .getConnection()).getChannel().pipeline().get(CommonTransformer.HANDLER_DECODER_NAME);
+ if (viaDecoder instanceof FabricDecodeHandler) {
+ UserConnection connection = ((FabricDecodeHandler) viaDecoder).getInfo();
+ ProtocolInfo protocol = connection.getProtocolInfo();
+ if (protocol != null) {
+ ProtocolVersion serverVer = protocol.serverProtocolVersion();
+ ProtocolVersion clientVer = protocol.protocolVersion();
+ line += " / C: " + clientVer + " S: " + serverVer + " A: " + connection.isActive();
+ }
+ }
+
+ info.getReturnValue().add(line);
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinRegistrySyncManager.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinRegistrySyncManager.java
new file mode 100644
index 0000000..a1af42d
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/debug/client/MixinRegistrySyncManager.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.debug.client;
+
+import com.viaversion.fabric.mc121.ViaFabric;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import net.fabricmc.fabric.impl.registry.sync.RegistrySyncManager;
+import net.minecraft.util.Identifier;
+import org.slf4j.Logger;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.Map;
+
+@Mixin(value = RegistrySyncManager.class, remap = false)
+public class MixinRegistrySyncManager {
+
+ @Shadow
+ @Final
+ private static Logger LOGGER;
+
+ @Inject(method = "checkRemoteRemap", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Ljava/lang/String;)V", ordinal = 0), cancellable = true)
+ private static void ignoreMissingRegistries(Map> map, CallbackInfo ci) {
+ if (ViaFabric.config.isIgnoreRegistrySyncErrors()) {
+ LOGGER.warn("Ignoring missing registries");
+ ci.cancel();
+ }
+ }
+
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinMultiplayerServerListPingerListener.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinMultiplayerServerListPingerListener.java
new file mode 100644
index 0000000..d50620b
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinMultiplayerServerListPingerListener.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.gui.client;
+
+import com.viaversion.fabric.common.gui.ViaServerInfo;
+import com.viaversion.fabric.common.handler.FabricDecodeHandler;
+import com.viaversion.fabric.mc121.mixin.debug.client.MixinClientConnectionAccessor;
+import net.minecraft.client.network.ServerInfo;
+import net.minecraft.network.ClientConnection;
+import net.minecraft.network.listener.ClientQueryPacketListener;
+import net.minecraft.network.packet.s2c.query.QueryResponseS2CPacket;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(targets = "net.minecraft.client.network.MultiplayerServerListPinger$1")
+public abstract class MixinMultiplayerServerListPingerListener implements ClientQueryPacketListener {
+ @Accessor
+ abstract ClientConnection getField_3774(); // Synthetic
+
+ @Accessor
+ abstract ServerInfo getField_3776(); // Synthetic
+
+ @Inject(method = "onResponse(Lnet/minecraft/network/packet/s2c/query/QueryResponseS2CPacket;)V", at = @At(value = "HEAD"))
+ private void onResponseCaptureServerInfo(QueryResponseS2CPacket packet, CallbackInfo ci) {
+ FabricDecodeHandler decoder = ((MixinClientConnectionAccessor) this.getField_3774()).getChannel()
+ .pipeline().get(FabricDecodeHandler.class);
+ if (decoder != null) {
+ ((ViaServerInfo) getField_3776()).viaFabric$setTranslating(decoder.getInfo().isActive());
+ ((ViaServerInfo) getField_3776()).viaFabric$setServerVer(decoder.getInfo().getProtocolInfo().getServerProtocolVersion());
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerEntry.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerEntry.java
new file mode 100644
index 0000000..cbdb122
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerEntry.java
@@ -0,0 +1,61 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.gui.client;
+
+import com.google.common.collect.Lists;
+import com.viaversion.fabric.common.gui.ViaServerInfo;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
+import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget;
+import net.minecraft.client.network.ServerInfo;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+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.ModifyArg;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mixin(MultiplayerServerListWidget.ServerEntry.class)
+public class MixinServerEntry {
+ @Shadow
+ @Final
+ private ServerInfo server;
+
+ @ModifyArg(method = "render", at = @At(value = "INVOKE", ordinal = 0,
+ target = "Lnet/minecraft/client/gui/DrawContext;drawGuiTexture(Lnet/minecraft/util/Identifier;IIII)V"))
+ private Identifier redirectPingIcon(Identifier texture) {
+ if (((ViaServerInfo) this.server).viaFabric$translating() && texture.getPath().startsWith("server_list/ping")) {
+ return Identifier.of("viafabric", texture.getPath());
+ }
+ return texture;
+ }
+
+ @Redirect(method = "render", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/client/gui/screen/multiplayer/MultiplayerScreen;setTooltip(Lnet/minecraft/text/Text;)V"))
+ private void addServerVer(MultiplayerScreen instance, Text text) {
+ ProtocolVersion proto = ProtocolVersion.getProtocol(((ViaServerInfo) this.server).viaFabric$getServerVer());
+ List lines = new ArrayList<>();
+ lines.add(text);
+ lines.add(Text.translatable("gui.ping_version.translated", proto.getName(), proto.getVersion()));
+ instance.setTooltip(Lists.transform(lines, Text::asOrderedText));
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerInfo.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerInfo.java
new file mode 100644
index 0000000..774456c
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/gui/client/MixinServerInfo.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.gui.client;
+
+import com.viaversion.fabric.common.gui.ViaServerInfo;
+import net.minecraft.client.network.ServerInfo;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+
+@Mixin(ServerInfo.class)
+public class MixinServerInfo implements ViaServerInfo {
+ @Unique
+ private boolean viaFabric$translating;
+
+ @Unique
+ private int viaFabric$serverVer;
+
+ @Override
+ public int viaFabric$getServerVer() {
+ return viaFabric$serverVer;
+ }
+
+ @Override
+ public void viaFabric$setServerVer(int ver) {
+ this.viaFabric$serverVer = ver;
+ }
+
+ @Override
+ public boolean viaFabric$translating() {
+ return viaFabric$translating;
+ }
+
+ @Override
+ public void viaFabric$setTranslating(boolean via) {
+ this.viaFabric$translating = via;
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/MixinClientConnection.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/MixinClientConnection.java
new file mode 100644
index 0000000..ce928d5
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/MixinClientConnection.java
@@ -0,0 +1,68 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.pipeline;
+
+import com.viaversion.fabric.common.handler.CommonTransformer;
+import com.viaversion.fabric.common.handler.FabricDecodeHandler;
+import com.viaversion.fabric.common.handler.FabricEncodeHandler;
+import com.viaversion.fabric.common.handler.PipelineReorderEvent;
+import com.viaversion.fabric.common.protocol.HostnameParserProtocol;
+import com.viaversion.viaversion.api.connection.UserConnection;
+import com.viaversion.viaversion.api.protocol.ProtocolPipeline;
+import com.viaversion.viaversion.connection.UserConnectionImpl;
+import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import net.minecraft.network.ClientConnection;
+import net.minecraft.network.NetworkSide;
+import net.minecraft.network.handler.HandlerNames;
+import net.minecraft.network.handler.PacketSizeLogger;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ClientConnection.class)
+public class MixinClientConnection {
+ @Shadow
+ private Channel channel;
+
+ @Inject(method = "setCompressionThreshold", at = @At("RETURN"))
+ private void reorderCompression(int compressionThreshold, boolean rejectBad, CallbackInfo ci) {
+ channel.pipeline().fireUserEventTriggered(new PipelineReorderEvent());
+ }
+
+ @Inject(method = "addHandlers", at = @At("RETURN"))
+ private static void onAddHandlers(ChannelPipeline pipeline, NetworkSide side, boolean local, PacketSizeLogger packetSizeLogger, CallbackInfo ci) {
+ final Channel channel = pipeline.channel();
+ if (channel instanceof SocketChannel) {
+ final UserConnection user = new UserConnectionImpl(channel, side == NetworkSide.CLIENTBOUND);
+ final ProtocolPipeline protocolPipeline = new ProtocolPipelineImpl(user);
+
+ final boolean clientSide = user.isClientSide();
+ if (clientSide) {
+ protocolPipeline.add(HostnameParserProtocol.INSTANCE);
+ }
+
+ pipeline.addBefore(clientSide ? HandlerNames.ENCODER : HandlerNames.OUTBOUND_CONFIG, CommonTransformer.HANDLER_ENCODER_NAME, new FabricEncodeHandler(user));
+ pipeline.addBefore(clientSide ? HandlerNames.INBOUND_CONFIG : HandlerNames.DECODER, CommonTransformer.HANDLER_DECODER_NAME, new FabricDecodeHandler(user));
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/client/MixinClientConnection.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/client/MixinClientConnection.java
new file mode 100644
index 0000000..01fc6b1
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/pipeline/client/MixinClientConnection.java
@@ -0,0 +1,44 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.pipeline.client;
+
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.fabric.mc121.service.ProtocolAutoDetector;
+import io.netty.channel.ChannelFuture;
+import net.minecraft.network.ClientConnection;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+@Mixin(ClientConnection.class)
+public class MixinClientConnection {
+ @Inject(method = "connect(Ljava/net/InetSocketAddress;ZLnet/minecraft/network/ClientConnection;)Lio/netty/channel/ChannelFuture;", at = @At("HEAD"))
+ private static void onConnect(InetSocketAddress address, boolean useEpoll, ClientConnection connection, CallbackInfoReturnable cir) {
+ try {
+ if (!ViaFabric.config.isClientSideEnabled()) return;
+ ProtocolAutoDetector.detectVersion(address).get(10, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ ViaFabric.JLOGGER.log(Level.WARNING, "Could not auto-detect protocol for " + address + " " + e);
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/shutdown/server/MixinMinecraftServer.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/shutdown/server/MixinMinecraftServer.java
new file mode 100644
index 0000000..123d3dc
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/mixin/shutdown/server/MixinMinecraftServer.java
@@ -0,0 +1,34 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.mixin.shutdown.server;
+
+import com.viaversion.viaversion.ViaManagerImpl;
+import com.viaversion.viaversion.api.Via;
+import net.minecraft.server.MinecraftServer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(MinecraftServer.class)
+public class MixinMinecraftServer {
+ @Inject(method = "stop", at = @At("TAIL"))
+ private void onStop(CallbackInfo ci) {
+ ((ViaManagerImpl) Via.getManager()).destroy();
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricNativeVersionProvider.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricNativeVersionProvider.java
new file mode 100644
index 0000000..631a56b
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricNativeVersionProvider.java
@@ -0,0 +1,28 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.platform;
+
+import com.viaversion.fabric.common.platform.NativeVersionProvider;
+import net.minecraft.SharedConstants;
+
+public class FabricNativeVersionProvider implements NativeVersionProvider {
+ @Override
+ public int getNativeServerVersion() {
+ return SharedConstants.getGameVersion().getProtocolVersion();
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricPlatform.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricPlatform.java
new file mode 100644
index 0000000..b069451
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/FabricPlatform.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.platform;
+
+import com.viaversion.fabric.common.commands.UserCommandSender;
+import com.viaversion.fabric.common.platform.NativeVersionProvider;
+import com.viaversion.fabric.common.provider.AbstractFabricPlatform;
+import com.viaversion.fabric.common.util.FutureTaskId;
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.fabric.mc121.commands.NMSCommandSender;
+import com.viaversion.viaversion.api.Via;
+import com.viaversion.viaversion.api.command.ViaCommandSender;
+import io.netty.channel.EventLoop;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.Entity;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerPlayerEntity;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+
+public class FabricPlatform extends AbstractFabricPlatform {
+ public static MinecraftServer getServer() {
+ if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+ return getIntegratedServer();
+ }
+ return (MinecraftServer) FabricLoader.getInstance().getGameInstance();
+ }
+
+ @Environment(EnvType.CLIENT)
+ private static MinecraftServer getIntegratedServer() {
+ return MinecraftClient.getInstance().getServer();
+ }
+
+ @Override
+ public FutureTaskId runSync(Runnable runnable) {
+ if (getServer() != null) {
+ return runServerSync(runnable);
+ } else {
+ return runEventLoop(runnable);
+ }
+ }
+
+ private FutureTaskId runServerSync(Runnable runnable) {
+ // Kick task needs to be on main thread, it does already have error logger
+ return new FutureTaskId(CompletableFuture.runAsync(runnable, getServer()));
+ }
+
+ @Override
+ public ViaCommandSender[] getOnlinePlayers() {
+ MinecraftServer server = getServer();
+ if (server != null && server.isOnThread()) {
+ return getServerPlayers();
+ }
+ return Via.getManager().getConnectionManager().getConnectedClients().values().stream()
+ .map(UserCommandSender::new)
+ .toArray(ViaCommandSender[]::new);
+ }
+
+ private ViaCommandSender[] getServerPlayers() {
+ return getServer().getPlayerManager().getPlayerList().stream()
+ .map(Entity::getCommandSource)
+ .map(NMSCommandSender::new)
+ .toArray(ViaCommandSender[]::new);
+ }
+
+ @Override
+ public void sendMessage(UUID uuid, String s) {
+ sendMessageServer(uuid, s);
+ }
+
+ private void sendMessageServer(UUID uuid, String s) {
+ MinecraftServer server = getServer();
+ if (server == null) return;
+ runServerSync(() -> {
+ ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
+ if (player == null) return;
+ player.sendMessage(NMSCommandSender.fromLegacy(s), false);
+ });
+ }
+
+ @Override
+ public boolean kickPlayer(UUID uuid, String s) {
+ return kickServer(uuid, s);
+ }
+
+ private boolean kickServer(UUID uuid, String s) {
+ MinecraftServer server = getServer();
+ if (server == null) return false;
+ Supplier kickTask = () -> {
+ ServerPlayerEntity player = server.getPlayerManager().getPlayer(uuid);
+ if (player == null) return false;
+ player.networkHandler.disconnect(NMSCommandSender.fromLegacy(s));
+ return true;
+ };
+ if (server.isOnThread()) {
+ return kickTask.get();
+ } else {
+ ViaFabric.JLOGGER.log(Level.WARNING, "Weird!? Player kicking was called off-thread", new Throwable());
+ runServerSync(kickTask::get);
+ }
+ return false; // Can't know if it worked
+ }
+
+ @Override
+ protected void installNativeVersionProvider() {
+ Via.getManager().getProviders().use(NativeVersionProvider.class, new FabricNativeVersionProvider());
+ }
+
+ @Override
+ protected ExecutorService asyncService() {
+ return ViaFabric.ASYNC_EXECUTOR;
+ }
+
+ @Override
+ protected EventLoop eventLoop() {
+ return ViaFabric.EVENT_LOOP;
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/VFLoader.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/VFLoader.java
new file mode 100644
index 0000000..deadc05
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/platform/VFLoader.java
@@ -0,0 +1,54 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.platform;
+
+import com.viaversion.fabric.mc121.providers.FabricVersionProvider;
+import com.viaversion.fabric.mc121.providers.VFHandItemProvider;
+import com.viaversion.fabric.mc121.providers.VFPlayerAbilitiesProvider;
+import com.viaversion.fabric.mc121.providers.VFPlayerLookTargetProvider;
+import com.viaversion.viaversion.api.Via;
+import com.viaversion.viaversion.api.platform.ViaPlatformLoader;
+import com.viaversion.viaversion.api.protocol.version.VersionProvider;
+import com.viaversion.viaversion.protocols.v1_12_2to1_13.provider.PlayerLookTargetProvider;
+import com.viaversion.viaversion.protocols.v1_15_2to1_16.provider.PlayerAbilitiesProvider;
+import com.viaversion.viaversion.protocols.v1_8to1_9.provider.HandItemProvider;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.loader.api.FabricLoader;
+
+public class VFLoader implements ViaPlatformLoader {
+ @Override
+ public void load() {
+ Via.getManager().getProviders().use(VersionProvider.class, new FabricVersionProvider());
+
+ if (Via.getPlatform().getConf().isItemCache()) {
+ VFHandItemProvider handProvider = new VFHandItemProvider();
+ if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+ handProvider.registerClientTick();
+ }
+ Via.getManager().getProviders().use(HandItemProvider.class, handProvider);
+ }
+
+ Via.getManager().getProviders().use(PlayerAbilitiesProvider.class, new VFPlayerAbilitiesProvider());
+ Via.getManager().getProviders().use(PlayerLookTargetProvider.class, new VFPlayerLookTargetProvider());
+ }
+
+ @Override
+ public void unload() {
+ // Nothing to do
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/FabricVersionProvider.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/FabricVersionProvider.java
new file mode 100644
index 0000000..dbb0753
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/FabricVersionProvider.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.providers;
+
+import com.viaversion.fabric.common.config.VFConfig;
+import com.viaversion.fabric.common.provider.AbstractFabricVersionProvider;
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.fabric.mc121.service.ProtocolAutoDetector;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import io.netty.channel.ChannelPipeline;
+import net.minecraft.network.ClientConnection;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Logger;
+
+public class FabricVersionProvider extends AbstractFabricVersionProvider {
+ @Override
+ protected Logger getLogger() {
+ return ViaFabric.JLOGGER;
+ }
+
+ @Override
+ protected VFConfig getConfig() {
+ return ViaFabric.config;
+ }
+
+ @Override
+ protected CompletableFuture detectVersion(InetSocketAddress address) {
+ return ProtocolAutoDetector.detectVersion(address);
+ }
+
+ @Override
+ protected boolean isMulticonnectHandler(ChannelPipeline pipe) {
+ return pipe.get(ClientConnection.class).getPacketListener().getClass().getName().startsWith("net.earthcomputer.multiconnect");
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFHandItemProvider.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFHandItemProvider.java
new file mode 100644
index 0000000..31a707e
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFHandItemProvider.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.providers;
+
+import com.viaversion.fabric.common.util.RemappingUtil;
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.viaversion.api.connection.UserConnection;
+import com.viaversion.viaversion.api.minecraft.item.DataItem;
+import com.viaversion.viaversion.api.minecraft.item.Item;
+import com.viaversion.viaversion.protocols.v1_8to1_9.provider.HandItemProvider;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.registry.Registries;
+import net.minecraft.util.Identifier;
+
+public class VFHandItemProvider extends HandItemProvider {
+ public Item clientItem = null;
+
+ @Override
+ public Item getHandItem(UserConnection info) {
+ if (info.isClientSide()) {
+ return getClientItem();
+ }
+ return super.getHandItem(info);
+ }
+
+ private Item getClientItem() {
+ if (clientItem == null) {
+ return new DataItem(0, (byte) 0, null);
+ }
+ return clientItem.copy();
+ }
+
+ @Environment(EnvType.CLIENT)
+ public void registerClientTick() {
+ try {
+ ClientTickEvents.END_WORLD_TICK.register(clientWorld -> tickClient());
+ } catch (NoClassDefFoundError ignored) {
+ ViaFabric.JLOGGER.info("Fabric Lifecycle V1 isn't installed");
+ }
+ }
+
+ private void tickClient() {
+ ClientPlayerEntity p = MinecraftClient.getInstance().player;
+ if (p != null) {
+ clientItem = fromNative(p.getInventory().getMainHandStack());
+ }
+ }
+
+ private Item fromNative(ItemStack original) {
+ Identifier iid = Registries.ITEM.getId(original.getItem());
+ int id = RemappingUtil.swordId(iid.toString());
+ return new DataItem(id, (byte) original.getCount(), (short) original.getDamage(), null);
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerAbilitiesProvider.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerAbilitiesProvider.java
new file mode 100644
index 0000000..e05a863
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerAbilitiesProvider.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.providers;
+
+import com.viaversion.viaversion.api.connection.UserConnection;
+import com.viaversion.viaversion.protocols.v1_15_2to1_16.provider.PlayerAbilitiesProvider;
+import net.minecraft.client.MinecraftClient;
+
+public class VFPlayerAbilitiesProvider extends PlayerAbilitiesProvider {
+
+ @Override
+ public float getFlyingSpeed(UserConnection connection) {
+ if (!connection.isClientSide()) return super.getFlyingSpeed(connection);
+
+ return MinecraftClient.getInstance().player.getAbilities().getFlySpeed();
+ }
+
+ @Override
+ public float getWalkingSpeed(UserConnection connection) {
+ if (!connection.isClientSide()) return super.getWalkingSpeed(connection);
+
+ return MinecraftClient.getInstance().player.getAbilities().getWalkSpeed();
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerLookTargetProvider.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerLookTargetProvider.java
new file mode 100644
index 0000000..f8e5464
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/providers/VFPlayerLookTargetProvider.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.providers;
+
+import com.viaversion.viaversion.api.connection.UserConnection;
+import com.viaversion.viaversion.api.minecraft.BlockPosition;
+import com.viaversion.viaversion.protocols.v1_12_2to1_13.provider.PlayerLookTargetProvider;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+
+public class VFPlayerLookTargetProvider extends PlayerLookTargetProvider {
+
+ @Override
+ public BlockPosition getPlayerLookTarget(UserConnection info) {
+ if (!info.isClientSide()) return null;
+
+ if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult) {
+ final BlockPos pos = blockHitResult.getBlockPos();
+ return new BlockPosition(pos.getX(), pos.getY(), pos.getZ());
+ }
+ return null;
+ }
+}
diff --git a/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/service/ProtocolAutoDetector.java b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/service/ProtocolAutoDetector.java
new file mode 100644
index 0000000..51ed734
--- /dev/null
+++ b/viafabric-mc121/src/main/java/com/viaversion/fabric/mc121/service/ProtocolAutoDetector.java
@@ -0,0 +1,160 @@
+/*
+ * This file is part of ViaFabric - https://github.com/ViaVersion/ViaFabric
+ * Copyright (C) 2018-2024 ViaVersion and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.viaversion.fabric.mc121.service;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.viaversion.fabric.common.AddressParser;
+import com.viaversion.fabric.mc121.ViaFabric;
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.SharedConstants;
+import net.minecraft.network.ClientConnection;
+import net.minecraft.network.DisconnectionInfo;
+import net.minecraft.network.NetworkSide;
+import net.minecraft.network.handler.EncoderHandler;
+import net.minecraft.network.handler.NetworkStateTransitions;
+import net.minecraft.network.handler.SizePrepender;
+import net.minecraft.network.handler.SplitterHandler;
+import net.minecraft.network.listener.ClientQueryPacketListener;
+import net.minecraft.network.packet.c2s.handshake.ConnectionIntent;
+import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket;
+import net.minecraft.network.packet.c2s.query.QueryRequestC2SPacket;
+import net.minecraft.network.packet.s2c.query.PingResultS2CPacket;
+import net.minecraft.network.packet.s2c.query.QueryResponseS2CPacket;
+import net.minecraft.network.state.HandshakeStates;
+import net.minecraft.network.state.QueryStates;
+import net.minecraft.server.ServerMetadata;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+
+@Environment(EnvType.CLIENT)
+public class ProtocolAutoDetector {
+ private static final LoadingCache> SERVER_VER = CacheBuilder.newBuilder()
+ .expireAfterWrite(30, TimeUnit.SECONDS)
+ .build(CacheLoader.from((address) -> {
+ CompletableFuture future = new CompletableFuture<>();
+
+ try {
+ final ClientConnection clientConnection = new ClientConnection(NetworkSide.CLIENTBOUND);
+
+ ChannelFuture ch = new Bootstrap()
+ .group(ClientConnection.CLIENT_IO_GROUP.get())
+ .channel(NioSocketChannel.class)
+ .handler(new ChannelInitializer<>() {
+ @Override
+ protected void initChannel(@NotNull Channel channel) {
+ try {
+ channel.config().setOption(ChannelOption.TCP_NODELAY, true);
+ channel.config().setOption(ChannelOption.IP_TOS, 0x18); // Stolen from Velocity, low delay, high reliability
+ } catch (ChannelException ignored) {
+ }
+
+ channel.pipeline()
+ .addLast("timeout", new ReadTimeoutHandler(30))
+ .addLast("splitter", new SplitterHandler(null))
+ .addLast("inbound_config", new NetworkStateTransitions.InboundConfigurer())
+ .addLast("prepender", new SizePrepender())
+ .addLast("encoder", new EncoderHandler<>(HandshakeStates.C2S))
+ .addLast("packet_handler", clientConnection);
+ }
+ })
+ .connect(address);
+
+ ch.addListener(future1 -> {
+ if (!future1.isSuccess()) {
+ future.completeExceptionally(future1.cause());
+ } else {
+ ch.channel().eventLoop().submit(() -> { // needs to execute after channel init
+ clientConnection.transitionInbound(QueryStates.S2C, new ClientQueryPacketListener() {
+ @Override
+ public void onResponse(QueryResponseS2CPacket packet) {
+ ServerMetadata meta = packet.metadata();
+ if (meta != null && meta.version().isPresent()) {
+ ProtocolVersion ver = ProtocolVersion.getProtocol(meta.version().get()
+ .protocolVersion());
+ future.complete(ver);
+ ViaFabric.JLOGGER.info("Auto-detected " + ver + " for " + address);
+ } else {
+ future.completeExceptionally(new IllegalArgumentException("Null version in query response"));
+ }
+ clientConnection.disconnect(Text.empty());
+ }
+
+ @Override
+ public void onPingResult(PingResultS2CPacket packet) {
+ clientConnection.disconnect(Text.literal("Pong not requested!"));
+ }
+
+ @Override
+ public void onDisconnected(DisconnectionInfo info) {
+ future.completeExceptionally(new IllegalStateException(info.reason().getString()));
+ }
+
+ @Override
+ public boolean isConnectionOpen() {
+ return ch.channel().isOpen();
+ }
+ });
+
+ //noinspection deprecation
+ clientConnection.send(new HandshakeC2SPacket(
+ SharedConstants.getGameVersion().getProtocolVersion(),
+ address.getHostString(),
+ address.getPort(),
+ ConnectionIntent.STATUS
+ ));
+
+ clientConnection.transitionOutbound(QueryStates.C2S);
+ clientConnection.send(QueryRequestC2SPacket.INSTANCE);
+ });
+ }
+ });
+ } catch (Throwable throwable) {
+ future.completeExceptionally(throwable);
+ }
+
+ return future;
+ }));
+
+ public static CompletableFuture detectVersion(InetSocketAddress address) {
+ try {
+ InetSocketAddress real = new InetSocketAddress(InetAddress.getByAddress
+ (new AddressParser().parse(address.getHostString()).serverAddress,
+ address.getAddress().getAddress()), address.getPort());
+ return SERVER_VER.get(real);
+ } catch (UnknownHostException | ExecutionException e) {
+ ViaFabric.JLOGGER.log(Level.WARNING, "Protocol auto detector error: ", e);
+ return CompletableFuture.completedFuture(null);
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/fabric.mod.json b/viafabric-mc121/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..9df2ff6
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/fabric.mod.json
@@ -0,0 +1,79 @@
+{
+ "schemaVersion": 1,
+ "id": "viafabric-mc121",
+ "name": "ViaFabric for 1.21",
+ "version": "${version}",
+ "description": "${description}",
+ "license": "GPL-3.0",
+ "contact": {
+ "homepage": "https://viaversion.com/fabric",
+ "issues": "https://github.com/ViaVersion/ViaFabric/issues",
+ "sources": "https://github.com/ViaVersion/ViaFabric"
+ },
+ "environment": "*",
+ "authors": [
+ {
+ "name": "creeper123123321",
+ "contact": {
+ "homepage": "https://creeper123123321.github.io/"
+ }
+ },
+ {
+ "name": "FlorianMichael/EnZaXD",
+ "contact": {
+ "homepage": "https://github.com/FlorianMichael"
+ }
+ },
+ {
+ "name": "kennytv",
+ "contact": {
+ "homepage": "https://kennytv.eu"
+ }
+ },
+ {
+ "name": "Kichura",
+ "contact": {
+ "homepage": "https://github.com/Kichura"
+ }
+ }
+ ],
+ "contributors": [
+ {
+ "name": "GitHub contributors",
+ "contact": {
+ "homepage": "https://github.com/ViaVersion/ViaFabric/graphs/contributors"
+ }
+ }
+ ],
+ "entrypoints": {
+ "main": [
+ "com.viaversion.fabric.mc121.ViaFabric"
+ ],
+ "client": [
+ "com.viaversion.fabric.mc121.ViaFabricClient"
+ ],
+ "modmenu": [
+ "com.viaversion.fabric.mc121.gui.ModMenuConfig"
+ ]
+ },
+ "depends": {
+ "fabric-resource-loader-v0": "*",
+ "minecraft": ["1.21"],
+ "viafabric": "*"
+ },
+ "recommends": {
+ "fabric-command-api-v2": "*"
+ },
+ "mixins": [
+ "mixins.viafabric121.address.json",
+ "mixins.viafabric121.gui.json",
+ "mixins.viafabric121.debug.json",
+ "mixins.viafabric121.pipeline.json",
+ "mixins.viafabric121.shutdown.json"
+ ],
+ "custom": {
+ "modmenu": {
+ "parent": "viafabric"
+ }
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/mixins.viafabric121.address.json b/viafabric-mc121/src/main/resources/mixins.viafabric121.address.json
new file mode 100644
index 0000000..531b0a5
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/mixins.viafabric121.address.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_21",
+ "package": "com.viaversion.fabric.mc121.mixin.address",
+ "mixins": [
+ ],
+ "client": [
+ "client.MixinAllowedAddressResolver"
+ ],
+ "injectors": {
+ "defaultRequire": 0
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/mixins.viafabric121.debug.json b/viafabric-mc121/src/main/resources/mixins.viafabric121.debug.json
new file mode 100644
index 0000000..a6b55b7
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/mixins.viafabric121.debug.json
@@ -0,0 +1,17 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_21",
+ "package": "com.viaversion.fabric.mc121.mixin.debug",
+ "mixins": [
+ "MixinRegistrySyncManager"
+ ],
+ "client": [
+ "client.MixinRegistrySyncManager",
+ "client.MixinClientConnection",
+ "client.MixinClientConnectionAccessor",
+ "client.MixinDebugHud"
+ ],
+ "injectors": {
+ "defaultRequire": 0
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/mixins.viafabric121.gui.json b/viafabric-mc121/src/main/resources/mixins.viafabric121.gui.json
new file mode 100644
index 0000000..04ad0a7
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/mixins.viafabric121.gui.json
@@ -0,0 +1,15 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_21",
+ "package": "com.viaversion.fabric.mc121.mixin.gui",
+ "mixins": [
+ ],
+ "client": [
+ "client.MixinMultiplayerServerListPingerListener",
+ "client.MixinServerEntry",
+ "client.MixinServerInfo"
+ ],
+ "injectors": {
+ "defaultRequire": 0
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/mixins.viafabric121.pipeline.json b/viafabric-mc121/src/main/resources/mixins.viafabric121.pipeline.json
new file mode 100644
index 0000000..6fc1c87
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/mixins.viafabric121.pipeline.json
@@ -0,0 +1,14 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_21",
+ "package": "com.viaversion.fabric.mc121.mixin.pipeline",
+ "mixins": [
+ "MixinClientConnection"
+ ],
+ "client": [
+ "client.MixinClientConnection"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/viafabric-mc121/src/main/resources/mixins.viafabric121.shutdown.json b/viafabric-mc121/src/main/resources/mixins.viafabric121.shutdown.json
new file mode 100644
index 0000000..d32e078
--- /dev/null
+++ b/viafabric-mc121/src/main/resources/mixins.viafabric121.shutdown.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_21",
+ "package": "com.viaversion.fabric.mc121.mixin.shutdown",
+ "mixins": [
+ ],
+ "server": [
+ "server.MixinMinecraftServer"
+ ],
+ "injectors": {
+ "defaultRequire": 0
+ }
+}