diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 633858b..a1f0b61 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ['https://viaversion.com/donate', 'https://viaversion.com/backwards', 'https://viaversion.com/rewind', 'https://creeper123123321.keybase.pub/#donate'] +custom: ['https://viaversion.com/donate', 'https://viaversion.com/backwards', 'https://viaversion.com/rewind', 'https://creeper123123321.github.io/#donate'] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8cae1e7..af78ca0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: java-version: 11 - name: build env: - curse_api_key: ${{ secrets.CREEPER_CF }} + CURSEFORGE_API_KEY: ${{ secrets.CREEPER_CF }} run: ./gradlew - name: capture build artifacts uses: actions/upload-artifact@v2 diff --git a/build.gradle b/build.gradle index 1d052dd..5968e0a 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,6 @@ class Globals { version = Globals.baseVersion + "+" + (ENV.GITHUB_RUN_NUMBER ? "" : "local-") + getBranch() logger.lifecycle("Building ViaFabric: " + version) -import org.apache.commons.codec.digest.DigestUtils import net.fabricmc.loom.task.RunClientTask import net.fabricmc.loom.task.RunServerTask @@ -118,13 +117,13 @@ allprojects { } jar { - classifier = "dev" + archiveClassifier = "dev" } afterEvaluate { remapJar { input = file("${project.buildDir}/libs/$archivesBaseName-${version}-dev.jar") - archiveName = "${archivesBaseName}-${version}.jar" + archiveFileName = "${archivesBaseName}-${version}.jar" } artifacts { @@ -134,8 +133,8 @@ allprojects { processResources { filesMatching("fabric.mod.json") { filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [ - version: version, - description: description + version : version, + description: description ]) } } @@ -220,7 +219,7 @@ subprojects { task remapMavenJar(type: net.fabricmc.loom.task.RemapJarTask, dependsOn: jar) { afterEvaluate { input = file("${project.buildDir}/libs/${archivesBaseName}-${version}-dev.jar") - archiveName = "${archivesBaseName}-${version}-maven.jar" + archiveFileName = "${archivesBaseName}-${version}-maven.jar" addNestedDependencies = false } } @@ -279,6 +278,10 @@ dependencies { mappings("net.fabricmc:yarn:1.8.9+build.202103291533:v2") modCompile("net.fabricmc:fabric-loader:0.10.5+build.213") + include("com.viaversion:viaversion:4.0.0-21w17a") + include("org.yaml:snakeyaml:1.28") + include("io.github.cottonmc:cotton-client-commands:1.0.0+1.15.2") + afterEvaluate { subprojects.each { compile project(path: ":${it.name}", configuration: "dev") @@ -298,11 +301,16 @@ curseforge { id = "391298" changelog = "A changelog can be found at https://github.com/ViaVersion/ViaFabric/commits" releaseType = "alpha" - addGameVersion "1.17" - addGameVersion "Fabric" + Arrays. asList("1.17", "1.16.5", "1.15.2", "1.14.4", "1.8.9", "Java 8", "Java 9", "Java 10", "Fabric") + .forEach { ver -> addGameVersion(ver) } mainArtifact(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar")) { displayName = "[${getBranch()}] ViaFabric $Globals.baseVersion" + relations { + requiredDependency("fabric-api") + requiredDependency("legacy-fabric-api") + embeddedLibrary("cotton-client-commands") + } } afterEvaluate { @@ -314,3 +322,9 @@ curseforge { forgeGradleIntegration = false } } + +if (getBranch() == "master" && ENV.CURSEFORGE_API_KEY && !ENV.CURSEFORGE_API_KEY.isEmpty()) { + defaultTasks("clean", "build", "curseforge") +} else { + defaultTasks("clean", "build") +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 66c77bb..fb36a66 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,4 +12,6 @@ rootProject.name = "viafabric" include "viafabric-mc18" include "viafabric-mc114" +include "viafabric-mc115" +include "viafabric-mc116" include "viafabric-mc117" \ No newline at end of file diff --git a/viafabric-mc114/src/main/resources/fabric.mod.json b/viafabric-mc114/src/main/resources/fabric.mod.json index 91aec0b..cdbfaab 100644 --- a/viafabric-mc114/src/main/resources/fabric.mod.json +++ b/viafabric-mc114/src/main/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "viafabric-impl", + "id": "viafabric-mc114", "name": "ViaFabric", "version": "@version@", "description": "@description@", diff --git a/viafabric-mc114/src/main/resources/mixins.viafabric.address.json b/viafabric-mc114/src/main/resources/mixins.viafabric.address.json index 8681aca..7757198 100644 --- a/viafabric-mc114/src/main/resources/mixins.viafabric.address.json +++ b/viafabric-mc114/src/main/resources/mixins.viafabric.address.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc114.mixin.address", "mixins": [ ], "client": [ - "client.MixinConnectScreenThread", - "client.MixinServerAddress", - "client.MixinServerPinger" + "com.viaversion.fabric.mc114.mixin.address.client.MixinConnectScreenThread", + "com.viaversion.fabric.mc114.mixin.address.client.MixinServerAddress", + "com.viaversion.fabric.mc114.mixin.address.client.MixinServerPinger" ], "injectors": { "defaultRequire": 1 diff --git a/viafabric-mc114/src/main/resources/mixins.viafabric.debug.json b/viafabric-mc114/src/main/resources/mixins.viafabric.debug.json index f450160..f52cda5 100644 --- a/viafabric-mc114/src/main/resources/mixins.viafabric.debug.json +++ b/viafabric-mc114/src/main/resources/mixins.viafabric.debug.json @@ -1,12 +1,11 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc114.mixin.debug", "mixins": [ ], "client": [ - "client.MixinClientConnectionAccessor", - "client.MixinDebugHud" + "com.viaversion.fabric.mc114.mixin.debug.client.MixinClientConnectionAccessor", + "com.viaversion.fabric.mc114.mixin.debug.client.MixinDebugHud" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc114/src/main/resources/mixins.viafabric.gui.json b/viafabric-mc114/src/main/resources/mixins.viafabric.gui.json index 7da61e9..de288c5 100644 --- a/viafabric-mc114/src/main/resources/mixins.viafabric.gui.json +++ b/viafabric-mc114/src/main/resources/mixins.viafabric.gui.json @@ -1,11 +1,10 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc114.mixin.gui", "mixins": [ ], "client": [ - "client.MixinMultiplayerScreen" + "com.viaversion.fabric.mc114.mixin.gui.client.MixinMultiplayerScreen" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc114/src/main/resources/mixins.viafabric.pipeline.json b/viafabric-mc114/src/main/resources/mixins.viafabric.pipeline.json index 26e4eda..2dfba78 100644 --- a/viafabric-mc114/src/main/resources/mixins.viafabric.pipeline.json +++ b/viafabric-mc114/src/main/resources/mixins.viafabric.pipeline.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc114.mixin.pipeline", "mixins": [ - "MixinClientConnection", - "MixinServerNetworkIoChInit" + "com.viaversion.fabric.mc114.mixin.pipeline.MixinClientConnection", + "com.viaversion.fabric.mc114.mixin.pipeline.MixinServerNetworkIoChInit" ], "client": [ - "client.MixinClientConnectionChInit" + "com.viaversion.fabric.mc114.mixin.pipeline.client.MixinClientConnectionChInit" ], "injectors": { "defaultRequire": 1 diff --git a/viafabric-mc115/build.gradle.kts b/viafabric-mc115/build.gradle.kts index ed6d60b..c344790 100644 --- a/viafabric-mc115/build.gradle.kts +++ b/viafabric-mc115/build.gradle.kts @@ -1,11 +1,10 @@ version = rootProject.version dependencies { - minecraft("com.mojang:minecraft:1.14.4") - mappings("net.fabricmc:yarn:1.14.4+build.16:v2") - modImplementation("net.fabricmc:fabric-loader:0.8.2+build.194") + minecraft("com.mojang:minecraft:1.15.2") + mappings("net.fabricmc:yarn:1.15.2+build.17:v2") + modImplementation("net.fabricmc:fabric-loader:0.9.3+build.207") - modImplementation("net.fabricmc.fabric-api:fabric-api:0.13.1+build.257-1.14") - modImplementation("io.github.prospector:modmenu:1.7.16.1.14.4+build.128") - modImplementation("io.github.cottonmc:cotton-client-commands:1.0.0+1.15.2") + modImplementation("net.fabricmc.fabric-api:fabric-api:0.19.0+build.325-1.15") + modImplementation("io.github.prospector:modmenu:1.10.2+build.32") } \ No newline at end of file diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabric.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabric.java new file mode 100644 index 0000000..6f6ef1d --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabric.java @@ -0,0 +1,110 @@ +package com.viaversion.fabric.mc115; + +import com.viaversion.fabric.mc115.commands.VRCommandHandler; +import com.viaversion.fabric.mc115.config.VRConfig; +import com.viaversion.fabric.mc115.platform.VRInjector; +import com.viaversion.fabric.mc115.platform.VRLoader; +import com.viaversion.fabric.mc115.platform.VRPlatform; +import com.viaversion.fabric.mc115.protocol.ViaFabricHostnameProtocol; +import com.viaversion.fabric.mc115.util.JLoggerToLog4j; +import com.google.common.collect.Range; +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 io.netty.channel.DefaultEventLoop; +import io.netty.channel.EventLoop; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.registry.CommandRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.server.command.CommandSource; +import org.apache.logging.log4j.LogManager; +import com.viaversion.viaversion.ViaManagerImpl; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingDataLoader; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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 CompletableFuture INIT_FUTURE = new CompletableFuture<>(); + public static VRConfig 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 String getVersion() { + return FabricLoader.getInstance().getModContainer("viafabric") + .get().getMetadata().getVersion().getFriendlyString(); + } + + public static LiteralArgumentBuilder command(String commandName) { + return LiteralArgumentBuilder.literal(commandName) + .then( + RequiredArgumentBuilder + .argument("args", StringArgumentType.greedyString()) + .executes(((VRCommandHandler) Via.getManager().getCommandHandler())::execute) + .suggests(((VRCommandHandler) Via.getManager().getCommandHandler())::suggestion) + ) + .executes(((VRCommandHandler) Via.getManager().getCommandHandler())::execute); + } + + @Override + public void onInitialize() { + Via.init(ViaManagerImpl.builder() + .injector(new VRInjector()) + .loader(new VRLoader()) + .commandHandler(new VRCommandHandler()) + .platform(new VRPlatform()).build()); + + FabricLoader.getInstance().getModContainer("viabackwards").ifPresent(mod -> MappingDataLoader.enableMappingsCache()); + + ((ViaManagerImpl) Via.getManager()).init(); + + Via.getManager().getProtocolManager().registerBaseProtocol(ViaFabricHostnameProtocol.INSTANCE, Range.lessThan(Integer.MIN_VALUE)); + ProtocolVersion.register(-2, "AUTO"); + + FabricLoader.getInstance().getEntrypoints("viafabric:via_api_initialized", Runnable.class).forEach(Runnable::run); + + try { + registerCommandsV1(); + } catch (NoClassDefFoundError ignored) { + try { + registerCommandsV0(); + JLOGGER.info("Using Fabric Commands V0"); + } catch (NoClassDefFoundError ignored2) { + JLOGGER.info("Couldn't register command as Fabric Commands isn't installed"); + } + } + + config = new VRConfig(FabricLoader.getInstance().getConfigDir().resolve("ViaFabric") + .resolve("viafabric.yml").toFile()); + + INIT_FUTURE.complete(null); + } + + private void registerCommandsV1() { + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("viaversion"))); + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("viaver"))); + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("vvfabric"))); + } + + @SuppressWarnings("deprecation") + private void registerCommandsV0() { + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("viaversion"))); + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("viaver"))); + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("vvfabric"))); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabricAddress.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabricAddress.java new file mode 100644 index 0000000..bdb773d --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/ViaFabricAddress.java @@ -0,0 +1,77 @@ +package com.viaversion.fabric.mc115; + +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +import java.util.Locale; + +public class ViaFabricAddress { + public int protocol = 0; + public String viaSuffix = null; + public String realAddress = null; + + public ViaFabricAddress parse(String address) { + if (address == null) return null; + String[] parts = address.split("\\."); + + boolean foundDomain = false; + boolean foundOptions = false; + + StringBuilder ourParts = new StringBuilder(); + StringBuilder realAddrBuilder = new StringBuilder(); + + for (int i = parts.length - 1; i >= 0; i--) { + String part = parts[i]; + boolean realAddrPart = false; + if (foundDomain) { + if (!foundOptions) { + if (part.startsWith("_")) { + String arg = part.substring(2); + if (part.toLowerCase(Locale.ROOT).startsWith("_v")) { + try { + protocol = Integer.parseInt(arg); + } catch (NumberFormatException e) { + ProtocolVersion closest = ProtocolVersion.getClosest(arg.replace("_", ".")); + if (closest != null) { + protocol = closest.getVersion(); + } + } + } + } else { + foundOptions = true; + } + } + if (foundOptions) { + realAddrPart = true; + } + } else if (part.equalsIgnoreCase("viafabric")) { + foundDomain = true; + } + if (realAddrPart) { + realAddrBuilder.insert(0, part + "."); + } else { + ourParts.insert(0, part + "."); + } + } + + String realAddr = realAddrBuilder.toString().replaceAll("\\.$", ""); + String suffix = ourParts.toString().replaceAll("\\.$", ""); + + if (realAddr.isEmpty()) { + this.realAddress = address; + } else { + this.realAddress = realAddr; + this.viaSuffix = suffix; + } + + return this; + } + + @Override + public String toString() { + return "ViaFabricAddress{" + + "protocol=" + protocol + + ", viaSuffix='" + viaSuffix + '\'' + + ", realAddress='" + realAddress + '\'' + + '}'; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/NMSCommandSender.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/NMSCommandSender.java new file mode 100644 index 0000000..9c45b4e --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/NMSCommandSender.java @@ -0,0 +1,61 @@ +package com.viaversion.fabric.mc115.commands; + +import io.github.cottonmc.clientcommands.CottonClientCommandSource; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.server.command.CommandSource; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +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); + } + + @Override + public void sendMessage(String s) { + if (source instanceof ServerCommandSource) { + ((ServerCommandSource) source).sendFeedback(Text.Serializer.fromJson(legacyToJson(s)), false); + } else if (source instanceof CottonClientCommandSource) { + ((CottonClientCommandSource) source).sendFeedback(Text.Serializer.fromJson(legacyToJson(s)), false); + } + } + + private String legacyToJson(String legacy) { + return GsonComponentSerializer.gson().serialize(LegacyComponentSerializer.legacySection().deserialize(legacy)); + } + + @Override + public UUID getUUID() { + if (source instanceof ServerCommandSource) { + Entity entity = ((ServerCommandSource) source).getEntity(); + if (entity != null) return entity.getUuid(); + } else if (source instanceof CottonClientCommandSource) { + return MinecraftClient.getInstance().player.getUuid(); + } + return UUID.fromString(getName()); + } + + @Override + public String getName() { + if (source instanceof ServerCommandSource) { + return ((ServerCommandSource) source).getName(); + } else if (source instanceof CottonClientCommandSource) { + return MinecraftClient.getInstance().player.getEntityName(); + } + return "?"; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/UserCommandSender.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/UserCommandSender.java new file mode 100644 index 0000000..36b45ac --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/UserCommandSender.java @@ -0,0 +1,35 @@ +package com.viaversion.fabric.mc115.commands; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.connection.UserConnection; + +import java.util.UUID; + +public class UserCommandSender implements ViaCommandSender { + private UserConnection con; + + public UserCommandSender(UserConnection con) { + this.con = con; + } + + @Override + public boolean hasPermission(String s) { + return false; + } + + @Override + public void sendMessage(String s) { + Via.getPlatform().sendMessage(getUUID(), s); + } + + @Override + public UUID getUUID() { + return con.getProtocolInfo().getUuid(); + } + + @Override + public String getName() { + return con.getProtocolInfo().getUsername(); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRClientCommands.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRClientCommands.java new file mode 100644 index 0000000..e780d3f --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRClientCommands.java @@ -0,0 +1,13 @@ +package com.viaversion.fabric.mc115.commands; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.mojang.brigadier.CommandDispatcher; +import io.github.cottonmc.clientcommands.ClientCommandPlugin; +import io.github.cottonmc.clientcommands.CottonClientCommandSource; + +public class VRClientCommands implements ClientCommandPlugin { + @Override + public void registerCommands(CommandDispatcher commandDispatcher) { + commandDispatcher.register(ViaFabric.command("viafabricclient")); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRCommandHandler.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRCommandHandler.java new file mode 100644 index 0000000..e5dfb08 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/VRCommandHandler.java @@ -0,0 +1,55 @@ +package com.viaversion.fabric.mc115.commands; + +import com.viaversion.fabric.mc115.commands.subs.LeakDetectSubCommand; +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 net.minecraft.server.command.CommandSource; +import com.viaversion.viaversion.commands.ViaCommandHandler; + +import java.util.concurrent.CompletableFuture; + +public class VRCommandHandler extends ViaCommandHandler { + { + try { + registerSubCommand(new LeakDetectSubCommand()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public int execute(CommandContext 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 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-mc115/src/main/java/com/viaversion/fabric/mc115/commands/subs/LeakDetectSubCommand.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/subs/LeakDetectSubCommand.java new file mode 100644 index 0000000..0d517a3 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/commands/subs/LeakDetectSubCommand.java @@ -0,0 +1,48 @@ +package com.viaversion.fabric.mc115.commands.subs; + +import io.netty.util.ResourceLeakDetector; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.command.ViaSubCommand; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class LeakDetectSubCommand extends ViaSubCommand { + @Override + public String name() { + return "leakdetect"; + } + + @Override + public String description() { + return "Sets ResourceLeakDetector level"; + } + + @Override + public boolean execute(ViaCommandSender viaCommandSender, String[] strings) { + if (strings.length == 1) { + try { + ResourceLeakDetector.Level level = ResourceLeakDetector.Level.valueOf(strings[0]); + ResourceLeakDetector.setLevel(level); + viaCommandSender.sendMessage("Set leak detector level to " + level); + } catch (IllegalArgumentException e) { + viaCommandSender.sendMessage("Invalid level (" + Arrays.toString(ResourceLeakDetector.Level.values()) + ")"); + } + } else { + viaCommandSender.sendMessage("Current leak detection level is " + ResourceLeakDetector.getLevel()); + } + return true; + } + + @Override + public List onTabComplete(ViaCommandSender sender, String[] args) { + if (args.length == 1) { + return Arrays.stream(ResourceLeakDetector.Level.values()) + .map(Enum::name) + .filter(it -> it.startsWith(args[0])) + .collect(Collectors.toList()); + } + return super.onTabComplete(sender, args); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/config/VRConfig.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/config/VRConfig.java new file mode 100644 index 0000000..4244d65 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/config/VRConfig.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc115.config; + +import com.viaversion.viaversion.util.Config; + +import java.io.File; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VRConfig extends Config { + public static final String ENABLE_CLIENT_SIDE = "enable-client-side"; + public static final String CLIENT_SIDE_VERSION = "client-side-version"; + public static final String CLIENT_SIDE_FORCE_DISABLE = "client-side-force-disable"; + public static final String HIDE_BUTTON = "hide-button"; + + public VRConfig(File configFile) { + super(configFile); + reloadConfig(); + } + + @Override + public URL getDefaultConfigURL() { + return getClass().getClassLoader().getResource("assets/viafabric/config.yml"); + } + + @Override + protected void handleConfig(Map map) { + } + + @Override + public List getUnsupportedOptions() { + return Collections.emptyList(); + } + + public boolean isClientSideEnabled() { + return getBoolean(ENABLE_CLIENT_SIDE, false); + } + + public void setClientSideEnabled(boolean val) { + set(ENABLE_CLIENT_SIDE, val); + } + + public int getClientSideVersion() { + return getInt(CLIENT_SIDE_VERSION, -1); + } + + public void setClientSideVersion(int val) { + set(CLIENT_SIDE_VERSION, val); + } + + public Collection getClientSideForceDisable() { + return (List) get(CLIENT_SIDE_FORCE_DISABLE, List.class, Collections.emptyList()); + } + + public void setHideButton(boolean val) { + set(HIDE_BUTTON, val); + } + + public boolean isHideButton() { + return getBoolean(HIDE_BUTTON, false); + } + + public boolean isForcedDisable(String line) { + return getClientSideForceDisable().contains(line); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ModMenuConfig.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ModMenuConfig.java new file mode 100644 index 0000000..a53d861 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ModMenuConfig.java @@ -0,0 +1,16 @@ +package com.viaversion.fabric.mc115.gui; + +import io.github.prospector.modmenu.api.ConfigScreenFactory; +import io.github.prospector.modmenu.api.ModMenuApi; + +public class ModMenuConfig implements ModMenuApi { + @Override + public String getModId() { + return "viafabric"; + } + + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ViaConfigScreen::new; + } +} \ No newline at end of file diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ViaConfigScreen.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ViaConfigScreen.java new file mode 100644 index 0000000..0c6d781 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/gui/ViaConfigScreen.java @@ -0,0 +1,169 @@ +package com.viaversion.fabric.mc115.gui; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.util.ProtocolUtils; +import com.viaversion.viaversion.api.Via; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +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.text.TranslatableText; + +import java.util.concurrent.CompletableFuture; + +@Environment(EnvType.CLIENT) +public class ViaConfigScreen extends Screen { + private static CompletableFuture latestProtocolSave; + private final Screen parent; + private TextFieldWidget protocolVersion; + + public ViaConfigScreen(Screen parent) { + super(new TranslatableText("gui.viafabric_config.title")); + this.parent = parent; + } + + private static int getProtocolTextColor(boolean valid, boolean supported) { + + if (!valid) { + return 0xff0000; // Red + } else if (!supported) { + return 0xFFA500; // Orange + } + return 0xE0E0E0; // Default + } + + @Override + protected void init() { + int entries = 0; + + this.addButton(new ButtonWidget(this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, getClientSideText().asString(), this::onClickClientSide)); + entries++; + + this.addButton(new ButtonWidget(this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, getHideViaButtonText().asString(), this::onHideViaButton)); + entries++; + + protocolVersion = new TextFieldWidget(this.font, + this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, new TranslatableText("gui.protocol_version_field.name").asString()); + entries++; + + protocolVersion.setTextPredicate(ProtocolUtils::isStartOfProtocolText); + protocolVersion.setChangedListener(this::onChangeVersionField); + int clientSideVersion = ViaFabric.config.getClientSideVersion(); + protocolVersion.setText(ProtocolUtils.getProtocolName(clientSideVersion)); + + this.children.add(protocolVersion); + + //noinspection ConstantConditions + if (entries % 2 == 1) { + entries++; + } + + this.addButton(new ButtonWidget(this.width / 2 - 100, this.height / 6 + 24 * (entries >> 1), 200, 20, new TranslatableText("gui.done").asString(), (buttonWidget) -> MinecraftClient.getInstance().openScreen(this.parent))); + } + + 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(ProtocolUtils.isSupported(newVersion, Via.getAPI().getServerVersion().lowestSupportedVersion()), + validProtocol)); + + int finalNewVersion = newVersion; + if (latestProtocolSave == null) latestProtocolSave = CompletableFuture.completedFuture(null); + ViaFabric.config.setClientSideVersion(finalNewVersion); + latestProtocolSave = latestProtocolSave.thenRunAsync(ViaFabric.config::saveConfig, ViaFabric.ASYNC_EXECUTOR); + } + + private void onClickClientSide(ButtonWidget widget) { + if (!ViaFabric.config.isClientSideEnabled()) { + MinecraftClient.getInstance().openScreen(new ConfirmScreen( + answer -> { + if (answer) { + ViaFabric.config.setClientSideEnabled(true); + ViaFabric.config.setClientSideVersion(-2); // AUTO + ViaFabric.config.saveConfig(); + widget.setMessage(getClientSideText().asString()); + } + MinecraftClient.getInstance().openScreen(this); + }, + new TranslatableText("gui.enable_client_side.question"), + new TranslatableText("gui.enable_client_side.warning"), + new TranslatableText("gui.enable_client_side.enable").asString(), + new TranslatableText("gui.cancel").asString() + )); + } else { + ViaFabric.config.setClientSideEnabled(false); + ViaFabric.config.saveConfig(); + } + widget.setMessage(getClientSideText().asString()); + } + + @Override + public void removed() { + ViaFabric.config.saveConfig(); + } + + @Override + public void onClose() { + MinecraftClient.getInstance().openScreen(this.parent); + } + + private TranslatableText getClientSideText() { + return ViaFabric.config.isClientSideEnabled() ? + new TranslatableText("gui.client_side.disable") + : new TranslatableText("gui.client_side.enable"); + } + + private TranslatableText getHideViaButtonText() { + return ViaFabric.config.isHideButton() ? + new TranslatableText("gui.hide_via_button.disable") : new TranslatableText("gui.hide_via_button.enable"); + } + + private void onHideViaButton(ButtonWidget widget) { + ViaFabric.config.setHideButton(!ViaFabric.config.isHideButton()); + ViaFabric.config.saveConfig(); + widget.setMessage(getHideViaButtonText().asString()); + } + + @Override + public void render(int mouseX, int mouseY, float delta) { + this.renderBackground(); + drawCenteredString(this.font, this.title.asString(), this.width / 2, 20, 16777215); + super.render(mouseX, mouseY, delta); + protocolVersion.render(mouseX, mouseY, delta); + } + + @Override + public void tick() { + super.tick(); + protocolVersion.tick(); + } +} + diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/CommonTransformer.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/CommonTransformer.java new file mode 100644 index 0000000..234d1ab --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/CommonTransformer.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc115.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.handler.codec.MessageToMessageDecoder; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; + +public class CommonTransformer { + public static final String HANDLER_DECODER_NAME = "via-decoder"; + public static final String HANDLER_ENCODER_NAME = "via-encoder"; + + public static void decompress(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + ChannelHandler handler = ctx.pipeline().get("decompress"); + ByteBuf decompressed = handler instanceof MessageToMessageDecoder + ? (ByteBuf) PipelineUtil.callDecode((MessageToMessageDecoder) handler, ctx, buf).get(0) + : (ByteBuf) PipelineUtil.callDecode((ByteToMessageDecoder) handler, ctx, buf).get(0); + try { + buf.clear().writeBytes(decompressed); + } finally { + decompressed.release(); + } + } + + public static void compress(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + ByteBuf compressed = ctx.alloc().buffer(); + try { + PipelineUtil.callEncode((MessageToByteEncoder) ctx.pipeline().get("compress"), ctx, buf, compressed); + buf.clear().writeBytes(compressed); + } finally { + compressed.release(); + } + } +} \ No newline at end of file diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricDecodeHandler.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricDecodeHandler.java new file mode 100644 index 0000000..bf09490 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricDecodeHandler.java @@ -0,0 +1,85 @@ +package com.viaversion.fabric.mc115.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.exception.CancelCodecException; +import com.viaversion.viaversion.exception.CancelDecoderException; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +@ChannelHandler.Sharable +public class FabricDecodeHandler extends MessageToMessageDecoder { + private final UserConnection info; + private boolean handledCompression; + private boolean skipDoubleTransform; + + public FabricDecodeHandler(UserConnection info) { + this.info = info; + } + + public UserConnection getInfo() { + return info; + } + + // https://github.com/ViaVersion/ViaVersion/blob/master/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityDecodeHandler.java + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + if (skipDoubleTransform) { + skipDoubleTransform = false; + out.add(bytebuf.retain()); + return; + } + + if (!info.checkIncomingPacket()) throw CancelDecoderException.generate(null); + if (!info.shouldTransformPacket()) { + out.add(bytebuf.retain()); + return; + } + + ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(bytebuf); + try { + boolean needsCompress = handleCompressionOrder(ctx, transformedBuf); + + info.transformIncoming(transformedBuf, CancelDecoderException::generate); + + if (needsCompress) { + CommonTransformer.compress(ctx, transformedBuf); + skipDoubleTransform = true; + } + out.add(transformedBuf.retain()); + } finally { + transformedBuf.release(); + } + } + + private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + if (handledCompression) return false; + + int decoderIndex = ctx.pipeline().names().indexOf("decompress"); + if (decoderIndex == -1) return false; + handledCompression = true; + if (decoderIndex > ctx.pipeline().names().indexOf("via-decoder")) { + // Need to decompress this packet due to bad order + CommonTransformer.decompress(ctx, buf); + ChannelHandler encoder = ctx.pipeline().get("via-encoder"); + ChannelHandler decoder = ctx.pipeline().get("via-decoder"); + ctx.pipeline().remove(encoder); + ctx.pipeline().remove(decoder); + ctx.pipeline().addAfter("compress", "via-encoder", encoder); + ctx.pipeline().addAfter("decompress", "via-decoder", decoder); + return true; + } + return false; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelCodecException.class)) return; + super.exceptionCaught(ctx, cause); + } +} \ No newline at end of file diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricEncodeHandler.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricEncodeHandler.java new file mode 100644 index 0000000..3105a14 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/FabricEncodeHandler.java @@ -0,0 +1,72 @@ +package com.viaversion.fabric.mc115.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.exception.CancelCodecException; +import com.viaversion.viaversion.exception.CancelEncoderException; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +@ChannelHandler.Sharable +public class FabricEncodeHandler extends MessageToMessageEncoder { + private final UserConnection info; + private boolean handledCompression; + + public FabricEncodeHandler(UserConnection info) { + this.info = info; + } + + @Override + protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null); + if (!info.shouldTransformPacket()) { + out.add(bytebuf.retain()); + return; + } + + ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(bytebuf); + try { + boolean needsCompress = handleCompressionOrder(ctx, transformedBuf); + + info.transformOutgoing(transformedBuf, CancelEncoderException::generate); + + if (needsCompress) { + CommonTransformer.compress(ctx, transformedBuf); + } + out.add(transformedBuf.retain()); + } finally { + transformedBuf.release(); + } + } + + private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + if (handledCompression) return false; + + int encoderIndex = ctx.pipeline().names().indexOf("compress"); + if (encoderIndex == -1) return false; + handledCompression = true; + if (encoderIndex > ctx.pipeline().names().indexOf("via-encoder")) { + // Need to decompress this packet due to bad order + CommonTransformer.decompress(ctx, buf); + ChannelHandler encoder = ctx.pipeline().get("via-encoder"); + ChannelHandler decoder = ctx.pipeline().get("via-decoder"); + ctx.pipeline().remove(encoder); + ctx.pipeline().remove(decoder); + ctx.pipeline().addAfter("compress", "via-encoder", encoder); + ctx.pipeline().addAfter("decompress", "via-decoder", decoder); + return true; + } + return false; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelCodecException.class)) return; + super.exceptionCaught(ctx, cause); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/clientside/ProtocolDetectionHandler.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/clientside/ProtocolDetectionHandler.java new file mode 100644 index 0000000..a04a8ff --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/handler/clientside/ProtocolDetectionHandler.java @@ -0,0 +1,79 @@ +package com.viaversion.fabric.mc115.handler.clientside; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.service.ProtocolAutoDetector; +import com.viaversion.viaversion.util.Pair; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; + +import java.net.InetSocketAddress; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class ProtocolDetectionHandler extends ChannelDuplexHandler { + private final Queue> queuedMessages = new ArrayDeque<>(); + private boolean hold = true; + private boolean pendentFlush; + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + if (ctx.channel().remoteAddress() instanceof InetSocketAddress) { + ScheduledFuture timeoutRun = ctx.executor().schedule(() -> { + ViaFabric.JLOGGER.warning("Timeout for protocol auto-detection in " + + ctx.channel().remoteAddress() + " server"); + hold = false; + drainQueue(ctx); + ctx.pipeline().remove(this); + }, 10, TimeUnit.SECONDS); + ProtocolAutoDetector.detectVersion(((InetSocketAddress) ctx.channel().remoteAddress())) + .whenComplete((obj, ex) -> { + ctx.pipeline().remove(this); + timeoutRun.cancel(false); + }); + // Let's cache it before we need it + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!hold) { + drainQueue(ctx); + super.write(ctx, msg, promise); + } else { + queuedMessages.add(new Pair<>(msg, promise)); + } + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + if (!hold) { + drainQueue(ctx); + super.flush(ctx); + } else { + pendentFlush = true; + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + drainQueue(ctx); + super.channelInactive(ctx); + } + + private void drainQueue(ChannelHandlerContext ctx) { + queuedMessages.forEach(it -> ctx.write(it.getKey(), it.getValue())); + queuedMessages.clear(); + if (pendentFlush) ctx.flush(); + pendentFlush = false; + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + drainQueue(ctx); + super.handlerRemoved(ctx); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinConnectScreenThread.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinConnectScreenThread.java new file mode 100644 index 0000000..73551d2 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinConnectScreenThread.java @@ -0,0 +1,24 @@ +package com.viaversion.fabric.mc115.mixin.address.client; + +import com.viaversion.fabric.mc115.ViaFabricAddress; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +@Mixin(targets = "net/minecraft/client/gui/screen/ConnectScreen$1", priority = 2000) +public class MixinConnectScreenThread { + @Redirect(method = "run()V", at = @At(value = "INVOKE", + target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;")) + private InetAddress resolveViaFabricAddr(String address) throws UnknownHostException { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return InetAddress.getByName(address); + } + + InetAddress resolved = InetAddress.getByName(viaAddr.realAddress); + return InetAddress.getByAddress(resolved.getHostName() + "." + viaAddr.viaSuffix, resolved.getAddress()); + } +} \ No newline at end of file diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerAddress.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerAddress.java new file mode 100644 index 0000000..db321ab --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerAddress.java @@ -0,0 +1,29 @@ +package com.viaversion.fabric.mc115.mixin.address.client; + +import com.viaversion.fabric.mc115.ViaFabricAddress; +import net.minecraft.network.ServerAddress; +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.Redirect; + +@Mixin(ServerAddress.class) +public abstract class MixinServerAddress { + @Shadow + private static String[] resolveSrv(String address) { + throw new AssertionError(); + } + + @Redirect(method = "parse", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ServerAddress;resolveSrv(Ljava/lang/String;)[Ljava/lang/String;")) + private static String[] modifySrvAddr(String address) { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return resolveSrv(address); + } + + String[] resolvedSrv = resolveSrv(viaAddr.realAddress); + resolvedSrv[0] = resolvedSrv[0].replaceAll("\\.$", "") + "." + viaAddr.viaSuffix; + + return resolvedSrv; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerPinger.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerPinger.java new file mode 100644 index 0000000..c1a766f --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/address/client/MixinServerPinger.java @@ -0,0 +1,25 @@ +package com.viaversion.fabric.mc115.mixin.address.client; + +import com.viaversion.fabric.mc115.ViaFabricAddress; +import net.minecraft.client.network.MultiplayerServerListPinger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +@Mixin(MultiplayerServerListPinger.class) +public class MixinServerPinger { + @Redirect(method = "add", at = @At(value = "INVOKE", + target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;")) + private InetAddress resolveViaFabricAddr(String address) throws UnknownHostException { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return InetAddress.getByName(address); + } + + InetAddress resolved = InetAddress.getByName(viaAddr.realAddress); + return InetAddress.getByAddress(resolved.getHostName() + "." + viaAddr.viaSuffix, resolved.getAddress()); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinClientConnectionAccessor.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinClientConnectionAccessor.java new file mode 100644 index 0000000..6a7e17d --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinClientConnectionAccessor.java @@ -0,0 +1,12 @@ +package com.viaversion.fabric.mc115.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-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinDebugHud.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinDebugHud.java new file mode 100644 index 0000000..ca330bb --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/debug/client/MixinDebugHud.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc115.mixin.debug.client; + +import com.viaversion.fabric.mc115.handler.CommonTransformer; +import com.viaversion.fabric.mc115.handler.FabricDecodeHandler; +import com.viaversion.viaversion.api.connection.ProtocolInfo; +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 com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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) { + ProtocolInfo protocol = ((FabricDecodeHandler) viaDecoder).getInfo().getProtocolInfo(); + if (protocol != null) { + ProtocolVersion serverVer = ProtocolVersion.getProtocol(protocol.getServerProtocolVersion()); + ProtocolVersion clientVer = ProtocolVersion.getProtocol(protocol.getProtocolVersion()); + line += " / C: " + clientVer.getName() + " (" + clientVer.getVersion() + ") S: " + + serverVer.getName() + " (" + serverVer.getVersion() + ") A: " + protocol.getUser().isActive(); + } + } + + info.getReturnValue().add(line); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/gui/client/MixinMultiplayerScreen.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/gui/client/MixinMultiplayerScreen.java new file mode 100644 index 0000000..7a7da5e --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/gui/client/MixinMultiplayerScreen.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc115.mixin.gui.client; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.gui.ViaConfigScreen; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +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.text.TranslatableText; +import net.minecraft.util.Identifier; +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(MultiplayerScreen.class) +public abstract class MixinMultiplayerScreen extends Screen { + protected MixinMultiplayerScreen(Text title, UnsupportedOperationException e) { + super(title); + throw e; + } + + @Inject(method = "init", at = @At("TAIL"), remap = false) + private void onInit(CallbackInfo ci) { + ButtonWidget enableClientSideViaVersion = new TexturedButtonWidget(this.width / 2 + 113, 10, + 40, 20, // Size + 0, 0, // Start pos of texture + 20, // v Hover offset + new Identifier("viafabric:textures/gui/widgets.png"), + 256, 256, // Texture size + it -> MinecraftClient.getInstance().openScreen(new ViaConfigScreen(this)), + new TranslatableText("gui.via_button").asString()); + if (ViaFabric.config.isHideButton()) enableClientSideViaVersion.visible = false; + addButton(enableClientSideViaVersion); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinClientConnection.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinClientConnection.java new file mode 100644 index 0000000..771c821 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinClientConnection.java @@ -0,0 +1,31 @@ +package com.viaversion.fabric.mc115.mixin.pipeline; + +import io.netty.channel.Channel; +import net.minecraft.network.ClientConnection; +import org.apache.logging.log4j.Logger; +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.Redirect; + + +@Mixin(ClientConnection.class) +public class MixinClientConnection { + @Shadow + private Channel channel; + + @Redirect( + method = "exceptionCaught", + remap = false, + at = @At( + value = "INVOKE", + target = "Lorg/apache/logging/log4j/Logger;debug(Ljava/lang/String;Ljava/lang/Throwable;)V" + )) + private void redirectDebug(Logger logger, String message, Throwable t) { + if ("Failed to sent packet".equals(message)) { + logger.info(message, t); + } else { + logger.debug(message, t); + } + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinServerNetworkIoChInit.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinServerNetworkIoChInit.java new file mode 100644 index 0000000..c5924fe --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/MixinServerNetworkIoChInit.java @@ -0,0 +1,28 @@ +package com.viaversion.fabric.mc115.mixin.pipeline; + +import com.viaversion.fabric.mc115.handler.CommonTransformer; +import com.viaversion.fabric.mc115.handler.FabricDecodeHandler; +import com.viaversion.fabric.mc115.handler.FabricEncodeHandler; +import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; +import io.netty.channel.Channel; +import io.netty.channel.socket.SocketChannel; +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; +import com.viaversion.viaversion.api.connection.UserConnection; + +@Mixin(targets = "net.minecraft.server.ServerNetworkIo$1") +public class MixinServerNetworkIoChInit { + @Inject(method = "initChannel", at = @At(value = "TAIL"), remap = false) + private void onInitChannel(Channel channel, CallbackInfo ci) { + if (channel instanceof SocketChannel) { + UserConnection user = new UserConnectionImpl(channel); + new ProtocolPipelineImpl(user); + + channel.pipeline().addBefore("encoder", CommonTransformer.HANDLER_ENCODER_NAME, new FabricEncodeHandler(user)); + channel.pipeline().addBefore("decoder", CommonTransformer.HANDLER_DECODER_NAME, new FabricDecodeHandler(user)); + } + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/client/MixinClientConnectionChInit.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/client/MixinClientConnectionChInit.java new file mode 100644 index 0000000..0ceca2b --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/mixin/pipeline/client/MixinClientConnectionChInit.java @@ -0,0 +1,35 @@ +package com.viaversion.fabric.mc115.mixin.pipeline.client; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.handler.CommonTransformer; +import com.viaversion.fabric.mc115.handler.FabricDecodeHandler; +import com.viaversion.fabric.mc115.handler.FabricEncodeHandler; +import com.viaversion.fabric.mc115.handler.clientside.ProtocolDetectionHandler; +import com.viaversion.fabric.mc115.protocol.ViaFabricHostnameProtocol; +import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; +import io.netty.channel.Channel; +import io.netty.channel.socket.SocketChannel; +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; +import com.viaversion.viaversion.api.connection.UserConnection; + +@Mixin(targets = "net.minecraft.network.ClientConnection$1") +public class MixinClientConnectionChInit { + @Inject(method = "initChannel", at = @At(value = "TAIL"), remap = false) + private void onInitChannel(Channel channel, CallbackInfo ci) { + if (channel instanceof SocketChannel) { + UserConnection user = new UserConnectionImpl(channel, true); + new ProtocolPipelineImpl(user).add(ViaFabricHostnameProtocol.INSTANCE); + + channel.pipeline() + .addBefore("encoder", CommonTransformer.HANDLER_ENCODER_NAME, new FabricEncodeHandler(user)) + .addBefore("decoder", CommonTransformer.HANDLER_DECODER_NAME, new FabricDecodeHandler(user)); + if (ViaFabric.config.isClientSideEnabled()) { + channel.pipeline().addAfter(CommonTransformer.HANDLER_ENCODER_NAME, "via-autoprotocol", new ProtocolDetectionHandler()); + } + } + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRInjector.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRInjector.java new file mode 100644 index 0000000..5e5d916 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRInjector.java @@ -0,0 +1,61 @@ +package com.viaversion.fabric.mc115.platform; + +import com.viaversion.fabric.mc115.handler.CommonTransformer; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import com.viaversion.viaversion.api.platform.ViaInjector; +import com.viaversion.viaversion.util.GsonUtil; +import com.viaversion.viaversion.libs.gson.JsonObject; + +import java.lang.reflect.Method; +import java.util.Arrays; + +public class VRInjector implements ViaInjector { + @Override + public void inject() { + // *looks at Mixins* + } + + @Override + public void uninject() { + // not possible *plays sad violin* + } + + @Override + public int getServerProtocolVersion() { + return SharedConstants.getGameVersion().getProtocolVersion(); + } + + @Override + public String getEncoderName() { + return CommonTransformer.HANDLER_ENCODER_NAME; + } + + @Override + public String getDecoderName() { + return CommonTransformer.HANDLER_DECODER_NAME; + } + + @Override + public JsonObject getDump() { + JsonObject obj = new JsonObject(); + try { + obj.add("serverNetworkIOChInit", GsonUtil.getGson().toJsonTree( + Arrays.stream(Class.forName("net.minecraft.class_3242$1").getDeclaredMethods()) + .map(Method::toString) + .toArray(String[]::new))); + } catch (ClassNotFoundException ignored) { + } + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + try { + obj.add("clientConnectionChInit", GsonUtil.getGson().toJsonTree( + Arrays.stream(Class.forName("net.minecraft.class_2535$1").getDeclaredMethods()) + .map(Method::toString) + .toArray(String[]::new))); + } catch (ClassNotFoundException ignored) { + } + } + return obj; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRLoader.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRLoader.java new file mode 100644 index 0000000..89bf1c2 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRLoader.java @@ -0,0 +1,34 @@ +package com.viaversion.fabric.mc115.platform; + +import com.viaversion.fabric.mc115.providers.VRHandItemProvider; +import com.viaversion.fabric.mc115.providers.VRVersionProvider; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.platform.ViaPlatformLoader; +import com.viaversion.viaversion.bungee.providers.BungeeMovementTransmitter; +import com.viaversion.viaversion.api.protocol.version.VersionProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider; + +public class VRLoader implements ViaPlatformLoader { + @Override + public void load() { + Via.getManager().getProviders().use(MovementTransmitterProvider.class, new BungeeMovementTransmitter()); + Via.getManager().getProviders().use(VersionProvider.class, new VRVersionProvider()); + + if (Via.getPlatform().getConf().isItemCache()) { + VRHandItemProvider handProvider = new VRHandItemProvider(); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + handProvider.registerClientTick(); + } + handProvider.registerServerTick(); + Via.getManager().getProviders().use(HandItemProvider.class, handProvider); + } + } + + @Override + public void unload() { + // Nothing to do + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRPlatform.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRPlatform.java new file mode 100644 index 0000000..5eb907c --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRPlatform.java @@ -0,0 +1,272 @@ +package com.viaversion.fabric.mc115.platform; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.commands.NMSCommandSender; +import com.viaversion.fabric.mc115.commands.UserCommandSender; +import com.viaversion.fabric.mc115.util.FutureTaskId; +import com.viaversion.fabric.mc115.util.JLoggerToLog4j; +import com.viaversion.viaversion.api.configuration.ViaVersionConfig; +import com.viaversion.viaversion.api.platform.PlatformTask; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.network.MessageType; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.apache.logging.log4j.LogManager; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.ViaAPI; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.configuration.ConfigurationProvider; +import com.viaversion.viaversion.api.platform.ViaPlatform; +import com.viaversion.viaversion.dump.PluginInfo; +import com.viaversion.viaversion.util.GsonUtil; +import com.viaversion.viaversion.libs.gson.JsonObject; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class VRPlatform implements ViaPlatform { + private final Logger logger = new JLoggerToLog4j(LogManager.getLogger("ViaVersion")); + private final VRViaConfig config; + private final File dataFolder; + private final ViaAPI api; + + public VRPlatform() { + Path configDir = FabricLoader.getInstance().getConfigDirectory().toPath().resolve("ViaFabric"); + config = new VRViaConfig(configDir.resolve("viaversion.yml").toFile()); + dataFolder = configDir.toFile(); + api = new VRViaAPI(); + } + + 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(); + } + + public static String legacyToJson(String legacy) { + return GsonComponentSerializer.gson().serialize(LegacyComponentSerializer.legacySection().deserialize(legacy)); + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public String getPlatformName() { + return "ViaFabric"; + } + + @Override + public String getPlatformVersion() { + return ViaFabric.getVersion(); + } + + @Override + public String getPluginVersion() { + return FabricLoader.getInstance().getModContainer("viaversion").map(ModContainer::getMetadata) + .map(ModMetadata::getVersion).map(Version::getFriendlyString).orElse("UNKNOWN"); + } + + @Override + public FutureTaskId runAsync(Runnable runnable) { + return new FutureTaskId(CompletableFuture + .runAsync(runnable, ViaFabric.ASYNC_EXECUTOR) + .exceptionally(throwable -> { + if (!(throwable instanceof CancellationException)) { + throwable.printStackTrace(); + } + return null; + }) + ); + } + + @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())); + } + + private FutureTaskId runEventLoop(Runnable runnable) { + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .submit(runnable) + .addListener(errorLogger()) + ); + } + + @Override + public PlatformTask runSync(Runnable runnable, Long ticks) { + // ViaVersion seems to not need to run delayed tasks on main thread + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .schedule(() -> runSync(runnable), ticks * 50, TimeUnit.MILLISECONDS) + .addListener(errorLogger()) + ); + } + + @Override + public PlatformTask runRepeatingSync(Runnable runnable, Long ticks) { + // ViaVersion seems to not need to run repeating tasks on main thread + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .scheduleAtFixedRate(() -> runSync(runnable), 0, ticks * 50, TimeUnit.MILLISECONDS) + .addListener(errorLogger()) + ); + } + + private > GenericFutureListener errorLogger() { + return future -> { + if (!future.isCancelled() && future.cause() != null) { + future.cause().printStackTrace(); + } + }; + } + + @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.sendChatMessage(Text.Serializer.fromJson(legacyToJson(s)), MessageType.SYSTEM); + }); + } + + @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(Text.Serializer.fromJson(legacyToJson(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 + public boolean isPluginEnabled() { + return true; + } + + @Override + public ViaAPI getApi() { + return api; + } + + @Override + public ViaVersionConfig getConf() { + return config; + } + + @Override + public ConfigurationProvider getConfigurationProvider() { + return config; + } + + @Override + public File getDataFolder() { + return dataFolder; + } + + @Override + public void onReload() { + // Nothing to do + } + + @Override + public JsonObject getDump() { + JsonObject platformSpecific = new JsonObject(); + List mods = new ArrayList<>(); + for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { + mods.add(new PluginInfo(true, + mod.getMetadata().getId() + " (" + mod.getMetadata().getName() + ")", + mod.getMetadata().getVersion().getFriendlyString(), + null, + mod.getMetadata().getAuthors().stream() + .map(info -> info.getName() + + (info.getContact().asMap().isEmpty() ? "" : " " + info.getContact().asMap())) + .collect(Collectors.toList()) + )); + } + + platformSpecific.add("mods", GsonUtil.getGson().toJsonTree(mods)); + return platformSpecific; + } + + @Override + public boolean isOldClientsAllowed() { + return true; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaAPI.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaAPI.java new file mode 100644 index 0000000..0177972 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaAPI.java @@ -0,0 +1,8 @@ +package com.viaversion.fabric.mc115.platform; + +import com.viaversion.viaversion.ViaAPIBase; + +import java.util.UUID; + +public class VRViaAPI extends ViaAPIBase { +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaConfig.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaConfig.java new file mode 100644 index 0000000..f474a7d --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/platform/VRViaConfig.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc115.platform; + +import com.viaversion.viaversion.configuration.AbstractViaConfig; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class VRViaConfig extends AbstractViaConfig { + // Based on Sponge ViaVersion + private static List UNSUPPORTED = Arrays.asList("anti-xray-patch", "bungee-ping-interval", + "bungee-ping-save", "bungee-servers", "quick-move-action-fix", "nms-player-ticking", + "velocity-ping-interval", "velocity-ping-save", "velocity-servers", + "blockconnection-method", "change-1_9-hitbox", "change-1_14-hitbox"); + + public VRViaConfig(File configFile) { + super(configFile); + // Load config + reloadConfig(); + } + + @Override + public URL getDefaultConfigURL() { + return getClass().getClassLoader().getResource("assets/viaversion/config.yml"); + } + + @Override + protected void handleConfig(Map config) { + // Nothing Currently + } + + @Override + public List getUnsupportedOptions() { + return UNSUPPORTED; + } + + @Override + public boolean isAntiXRay() { + return false; + } + + @Override + public boolean isNMSPlayerTicking() { + return false; + } + + @Override + public boolean is1_12QuickMoveActionFix() { + return false; + } + + @Override + public String getBlockConnectionMethod() { + return "packet"; + } + + @Override + public boolean is1_9HitboxFix() { + return false; + } + + @Override + public boolean is1_14HitboxFix() { + return false; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/protocol/ViaFabricHostnameProtocol.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/protocol/ViaFabricHostnameProtocol.java new file mode 100644 index 0000000..8028c40 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/protocol/ViaFabricHostnameProtocol.java @@ -0,0 +1,34 @@ +package com.viaversion.fabric.mc115.protocol; + +import com.viaversion.fabric.mc115.ViaFabricAddress; +import com.viaversion.viaversion.api.protocol.AbstractSimpleProtocol; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; +import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.api.protocol.packet.State; + +public class ViaFabricHostnameProtocol extends AbstractSimpleProtocol { + public static final ViaFabricHostnameProtocol INSTANCE = new ViaFabricHostnameProtocol(); + + @Override + protected void registerPackets() { + registerServerbound(State.HANDSHAKE, 0, 0, new PacketRemapper() { + @Override + public void registerMap() { + map(Type.VAR_INT); // Protocol version + map(Type.STRING, new ValueTransformer(Type.STRING) { + @Override + public String transform(PacketWrapper packetWrapper, String s) { + return new ViaFabricAddress().parse(s).realAddress; + } + }); + } + }); + } + + @Override + public boolean isBaseProtocol() { + return true; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRHandItemProvider.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRHandItemProvider.java new file mode 100644 index 0000000..2f1b904 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRHandItemProvider.java @@ -0,0 +1,110 @@ +package com.viaversion.fabric.mc115.providers; + +import com.viaversion.fabric.mc115.ViaFabric; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.world.WorldTickCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.item.Item; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class VRHandItemProvider extends HandItemProvider { + public Item clientItem = null; + public Map serverPlayers = new ConcurrentHashMap<>(); + + @Override + public Item getHandItem(UserConnection info) { + Item serverItem; + if (info.isClientSide()) { + return getClientItem(); + } else if ((serverItem = serverPlayers.get(info.getProtocolInfo().getUuid())) != null) { + return new Item(serverItem); + } + return super.getHandItem(info); + } + + private Item getClientItem() { + if (clientItem == null) { + return new Item(0, (byte) 0, (short) 0, null); + } + return new Item(clientItem); + } + + @Environment(EnvType.CLIENT) + public void registerClientTick() { + try { + ClientTickEvents.END_WORLD_TICK.register(clientWorld -> tickClient()); + } catch (NoClassDefFoundError ignored) { + try { + WorldTickCallback.EVENT.register(world -> { + if (world.isClient) { + tickClient(); + } + }); + } catch (NoClassDefFoundError ignored2) { + ViaFabric.JLOGGER.info("Fabric Lifecycle V0/V1 isn't installed"); + } + } + } + + public void registerServerTick() { + try { + ServerTickEvents.END_WORLD_TICK.register(this::tickServer); + } catch (NoClassDefFoundError ignored) { + WorldTickCallback.EVENT.register(world -> { + if (!world.isClient) { + tickServer(world); + } + }); + } + } + + private void tickClient() { + ClientPlayerEntity p = MinecraftClient.getInstance().player; + if (p != null) { + clientItem = fromNative(p.inventory.getMainHandStack()); + } + } + + private void tickServer(World world) { + serverPlayers.clear(); + world.getPlayers().forEach(it -> serverPlayers + .put(it.getUuid(), fromNative(it.inventory.getMainHandStack()))); + } + + private Item fromNative(ItemStack original) { + Identifier iid = Registry.ITEM.getId(original.getItem()); + if (iid == null) return new Item(0, (byte) 0, (short) 0, null); + int id = swordId(iid.toString()); + return new Item(id, (byte) original.getCount(), (short) original.getDamage(), null); + } + + private int swordId(String id) { + // https://github.com/ViaVersion/ViaVersion/blob/8de26a0ad33f5b739f5394ed80f69d14197fddc7/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java#L86 + switch (id) { + case "minecraft:iron_sword": + return 267; + case "minecraft:wooden_sword": + return 268; + case "minecraft:golden_sword": + return 272; + case "minecraft:diamond_sword": + return 276; + case "minecraft:stone_sword": + return 283; + } + return 0; + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRVersionProvider.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRVersionProvider.java new file mode 100644 index 0000000..00af12f --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/providers/VRVersionProvider.java @@ -0,0 +1,166 @@ +package com.viaversion.fabric.mc115.providers; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.ViaFabricAddress; +import com.viaversion.fabric.mc115.service.ProtocolAutoDetector; +import com.viaversion.fabric.mc115.util.ProtocolUtils; +import com.google.common.primitives.Ints; +import com.viaversion.viaversion.api.connection.ProtocolInfo; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.network.ClientConnection; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.exception.CancelException; +import com.viaversion.viaversion.api.protocol.packet.State; +import com.viaversion.viaversion.protocols.base.*; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.*; +import java.util.stream.IntStream; + +public class VRVersionProvider extends BaseVersionProvider { + private int[] multiconnectSupportedVersions = null; + + { + try { + if (FabricLoader.getInstance().isModLoaded("multiconnect")) { + Class mcApiClass = Class.forName("net.earthcomputer.multiconnect.api.MultiConnectAPI"); + Class iProtocolClass = Class.forName("net.earthcomputer.multiconnect.api.IProtocol"); + Object mcApiInstance = mcApiClass.getMethod("instance").invoke(null); + List protocols = (List) mcApiClass.getMethod("getSupportedProtocols").invoke(mcApiInstance); + Method getValue = iProtocolClass.getMethod("getValue"); + Method isMulticonnectBeta; + try { + isMulticonnectBeta = iProtocolClass.getMethod("isMulticonnectBeta"); + } catch (NoSuchMethodException e) { + isMulticonnectBeta = null; + } + Set vers = new TreeSet<>(); + for (Object protocol : protocols) { + // Do not use versions with beta multiconnect support, which may have stability issues + if (isMulticonnectBeta == null || !(Boolean) isMulticonnectBeta.invoke(protocol)) { + vers.add((Integer) getValue.invoke(protocol)); + } + } + multiconnectSupportedVersions = vers.stream().mapToInt(Integer::intValue).toArray(); + ViaFabric.JLOGGER.info("ViaFabric will integrate with multiconnect"); + } + } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException + | ClassCastException ignored) { + } + } + + @Override + public int getClosestServerProtocol(UserConnection connection) throws Exception { + if (connection.isClientSide()) { + ProtocolInfo info = Objects.requireNonNull(connection.getProtocolInfo()); + + if (!ViaFabric.config.isClientSideEnabled()) { + return info.getProtocolVersion(); + } + + int serverVer = ViaFabric.config.getClientSideVersion(); + SocketAddress addr = connection.getChannel().remoteAddress(); + + if (addr instanceof InetSocketAddress) { + int addrVersion = new ViaFabricAddress().parse(((InetSocketAddress) addr).getHostName()).protocol; + if (addrVersion != 0) serverVer = addrVersion; + + try { + if (serverVer == -2) { + // Hope protocol was autodetected + ProtocolVersion autoVer = + ProtocolAutoDetector.detectVersion((InetSocketAddress) addr).getNow(null); + if (autoVer != null) { + serverVer = autoVer.getVersion(); + } + } + } catch (Exception e) { + ViaFabric.JLOGGER.warning("Couldn't auto detect: " + e); + } + } + + boolean blocked = checkAddressBlocked(addr); + boolean supported = ProtocolUtils.isSupported(serverVer, info.getProtocolVersion()); + + handleMulticonnectPing(connection, info, blocked, serverVer); + + if (blocked || !supported) serverVer = info.getProtocolVersion(); + + return serverVer; + } + return super.getClosestServerProtocol(connection); + } + + private boolean checkAddressBlocked(SocketAddress addr) { + return addr instanceof InetSocketAddress && (isDisabled(((InetSocketAddress) addr).getHostString()) + || ((((InetSocketAddress) addr).getAddress() != null) && + (isDisabled(((InetSocketAddress) addr).getAddress().getHostAddress()) + || isDisabled(((InetSocketAddress) addr).getAddress().getHostName())))); + } + + private void handleMulticonnectPing(UserConnection connection, ProtocolInfo info, boolean blocked, int serverVer) throws Exception { + if (info.getState() == State.STATUS + && info.getProtocolVersion() == -1 + && connection.getChannel().pipeline().get(ClientConnection.class).getPacketListener() + .getClass().getName().startsWith("net.earthcomputer.multiconnect") + && (blocked || ProtocolUtils.isSupported(serverVer, getVersionForMulticonnect(serverVer)))) { // Intercept the connection + int multiconnectSuggestion = blocked ? -1 : getVersionForMulticonnect(serverVer); + ViaFabric.JLOGGER.info("Sending " + ProtocolVersion.getProtocol(multiconnectSuggestion) + " for multiconnect version detector"); + PacketWrapper newAnswer = PacketWrapper.create(0x00, null, connection); + newAnswer.write(Type.STRING, "{\"version\":{\"name\":\"viafabric integration\",\"protocol\":" + multiconnectSuggestion + "}}"); + newAnswer.send(info.getPipeline().contains(BaseProtocol1_16.class) ? BaseProtocol1_16.class : BaseProtocol1_7.class, true, true); + throw CancelException.generate(); + } + } + + private int getVersionForMulticonnect(int clientSideVersion) { + // https://github.com/ViaVersion/ViaVersion/blob/master/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityVersionProvider.java + int[] compatibleProtocols = multiconnectSupportedVersions; + + if (Arrays.binarySearch(compatibleProtocols, clientSideVersion) >= 0) { + return clientSideVersion; + } + + if (clientSideVersion < compatibleProtocols[0]) { + return compatibleProtocols[0]; + } + + // TODO: This needs a better fix, i.e checking ProtocolRegistry to see if it would work. + for (int i = compatibleProtocols.length - 1; i >= 0; i--) { + int protocol = compatibleProtocols[i]; + if (clientSideVersion > protocol && ProtocolVersion.isRegistered(protocol)) { + return protocol; + } + } + + ViaFabric.JLOGGER.severe("multiconnect integration: Panic, no protocol id found for " + clientSideVersion); + return clientSideVersion; + } + + private boolean isDisabled(String addr) { + String[] parts = addr.split("\\."); + boolean isNumericIp = parts.length == 4 && Arrays.stream(parts).map(Ints::tryParse).allMatch(Objects::nonNull); + return IntStream.range(0, parts.length).anyMatch(i -> { + String query; + if (isNumericIp) { + query = String.join(".", Arrays.stream(parts, 0, i + 1) + .toArray(String[]::new)) + ((i != 3) ? ".*" : ""); + } else { + query = ((i != 0) ? "*." : "") + String.join(".", Arrays.stream(parts, i, parts.length) + .toArray(String[]::new)); + } + if (ViaFabric.config.isForcedDisable(query)) { + ViaFabric.JLOGGER.info(addr + " is force-disabled. (Matches " + query + ")"); + return true; + } else { + return false; + } + }); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/service/ProtocolAutoDetector.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/service/ProtocolAutoDetector.java new file mode 100644 index 0000000..762e1bd --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/service/ProtocolAutoDetector.java @@ -0,0 +1,125 @@ +package com.viaversion.fabric.mc115.service; + +import com.viaversion.fabric.mc115.ViaFabric; +import com.viaversion.fabric.mc115.ViaFabricAddress; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +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.network.*; +import net.minecraft.network.listener.ClientQueryPacketListener; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.network.packet.c2s.query.QueryRequestC2SPacket; +import net.minecraft.network.packet.s2c.query.QueryPongS2CPacket; +import net.minecraft.network.packet.s2c.query.QueryResponseS2CPacket; +import net.minecraft.server.ServerMetadata; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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 LoadingCache> SERVER_VER = CacheBuilder.newBuilder() + .expireAfterAccess(100, 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() { + protected void initChannel(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()) + .addLast("decoder", new DecoderHandler(NetworkSide.CLIENTBOUND)) + .addLast("prepender", new SizePrepender()) + .addLast("encoder", new PacketEncoder(NetworkSide.SERVERBOUND)) + .addLast("packet_handler", clientConnection); + } + }) + .connect(address); + + ch.addListener(future1 -> { + if (!future1.isSuccess()) { + future.completeExceptionally(future1.cause()); + } else { + ch.channel().eventLoop().execute(() -> { // needs to execute after channel init + clientConnection.setPacketListener(new ClientQueryPacketListener() { + @Override + public void onResponse(QueryResponseS2CPacket packet) { + ServerMetadata meta = packet.getServerMetadata(); + ServerMetadata.Version version; + if (meta != null && (version = meta.getVersion()) != null) { + ProtocolVersion ver = ProtocolVersion.getProtocol(version.getProtocolVersion()); + future.complete(ver); + ViaFabric.JLOGGER.info("Auto-detected " + ver + " for " + address); + } else { + future.completeExceptionally(new IllegalArgumentException("Null version in query response")); + } + clientConnection.disconnect(new LiteralText("")); + } + + @Override + public void onPong(QueryPongS2CPacket packet) { + clientConnection.disconnect(new LiteralText("Pong not requested!")); + } + + @Override + public void onDisconnected(Text reason) { + future.completeExceptionally(new IllegalStateException(reason.asString())); + } + + @Override + public ClientConnection getConnection() { + return clientConnection; + } + }); + + clientConnection.send(new HandshakeC2SPacket(address.getHostString(), + address.getPort(), NetworkState.STATUS)); + clientConnection.send(new QueryRequestC2SPacket()); + }); + } + }); + } catch (Throwable throwable) { + future.completeExceptionally(throwable); + } + + return future; + })); + + public static CompletableFuture detectVersion(InetSocketAddress address) { + try { + InetSocketAddress real = new InetSocketAddress(InetAddress.getByAddress + (new ViaFabricAddress().parse(address.getHostString()).realAddress, + 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-mc115/src/main/java/com/viaversion/fabric/mc115/util/FutureTaskId.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/FutureTaskId.java new file mode 100644 index 0000000..df97724 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/FutureTaskId.java @@ -0,0 +1,23 @@ +package com.viaversion.fabric.mc115.util; + +import com.viaversion.viaversion.api.platform.PlatformTask; + +import java.util.concurrent.Future; + +public class FutureTaskId implements PlatformTask> { + private final Future object; + + public FutureTaskId(Future object) { + this.object = object; + } + + @Override + public Future getObject() { + return object; + } + + @Override + public void cancel() { + object.cancel(false); + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/JLoggerToLog4j.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/JLoggerToLog4j.java new file mode 100644 index 0000000..64a7de7 --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/JLoggerToLog4j.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc115.util; + +import java.text.MessageFormat; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public class JLoggerToLog4j extends Logger { + private final org.apache.logging.log4j.Logger base; + + public JLoggerToLog4j(org.apache.logging.log4j.Logger logger) { + super("logger", null); + this.base = logger; + } + + public void log(LogRecord record) { + this.log(record.getLevel(), record.getMessage()); + } + + public void log(Level level, String msg) { + if (level == Level.FINE) { + this.base.debug(msg); + } else if (level == Level.WARNING) { + this.base.warn(msg); + } else if (level == Level.SEVERE) { + this.base.error(msg); + } else if (level == Level.INFO) { + this.base.info(msg); + } else { + this.base.trace(msg); + } + + } + + public void log(Level level, String msg, Object param1) { + if (level == Level.FINE) { + this.base.debug(msg, param1); + } else if (level == Level.WARNING) { + this.base.warn(msg, param1); + } else if (level == Level.SEVERE) { + this.base.error(msg, param1); + } else if (level == Level.INFO) { + this.base.info(msg, param1); + } else { + this.base.trace(msg, param1); + } + + } + + public void log(Level level, String msg, Object[] params) { + log(level, MessageFormat.format(msg, params)); + } + + public void log(Level level, String msg, Throwable params) { + if (level == Level.FINE) { + this.base.debug(msg, params); + } else if (level == Level.WARNING) { + this.base.warn(msg, params); + } else if (level == Level.SEVERE) { + this.base.error(msg, params); + } else if (level == Level.INFO) { + this.base.info(msg, params); + } else { + this.base.trace(msg, params); + } + + } +} diff --git a/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/ProtocolUtils.java b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/ProtocolUtils.java new file mode 100644 index 0000000..a73c3bd --- /dev/null +++ b/viafabric-mc115/src/main/java/com/viaversion/fabric/mc115/util/ProtocolUtils.java @@ -0,0 +1,60 @@ +package com.viaversion.fabric.mc115.util; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +import java.util.Arrays; +import java.util.stream.Stream; + +public class ProtocolUtils { + public static boolean isSupported(int server, int client) { + return server == client || Via.getManager().getProtocolManager().getProtocolPath(client, server) != null; + } + + public static String getProtocolName(int id) { + if (!ProtocolVersion.isRegistered(id)) return Integer.toString(id); + return ProtocolVersion.getProtocol(id).getName(); + } + + public static boolean isStartOfProtocolText(String s) { + try { + Integer.parseInt(s); + return true; + } catch (NumberFormatException e) { + try { + Integer.parseInt(s + '0'); + return true; + } catch (NumberFormatException e2) { + return ProtocolVersion.getProtocols().stream() + .map(ProtocolVersion::getName) + .flatMap(str -> Stream.concat( + Arrays.stream(str.split("-")), + Arrays.stream(new String[]{str}) + )) + .anyMatch(ver -> ver.startsWith(s)); + } + } + } + + public static Integer parseProtocolId(String s) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException ignored) { + ProtocolVersion closest = ProtocolVersion.getClosest(s); + if (closest == null) return null; + return closest.getVersion(); + } + } + + public static String[] getProtocolSuggestions(String text) { + return ProtocolVersion.getProtocols().stream() + .map(ProtocolVersion::getName) + .flatMap(str -> Stream.concat( + Arrays.stream(str.split("-")), + Arrays.stream(new String[]{str}) + )) + .distinct() + .filter(ver -> ver.startsWith(text)) + .toArray(String[]::new); + } +} diff --git a/viafabric-mc115/src/main/resources/fabric.mod.json b/viafabric-mc115/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..bd1fdfc --- /dev/null +++ b/viafabric-mc115/src/main/resources/fabric.mod.json @@ -0,0 +1,61 @@ +{ + "schemaVersion": 1, + "id": "viafabric-mc115", + "name": "ViaFabric", + "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/" + } + } + ], + "contributors": [ + { + "name": "GitHub contributors", + "contact": { + "homepage": "https://github.com/ViaVersion/ViaFabric/graphs/contributors" + } + } + ], + "entrypoints": { + "main": [ + "com.viaversion.fabric.mc115.ViaFabric" + ], + "cotton-client-commands": [ + "com.viaversion.fabric.mc115.commands.VRClientCommands" + ], + "modmenu": [ + "com.viaversion.fabric.mc115.gui.ModMenuConfig" + ] + }, + "depends": { + "cotton-client-commands": "*", + "fabricloader": ">=0.4.0", + "fabric-resource-loader-v0": "*", + "minecraft": "1.15.x", + "viaversion": ">3.0.1" + }, + "conflicts": { + "fabric-registry-sync-v0": "*" + }, + "recommends": { + "fabric-command-api-v1": "*" + }, + "icon": "assets/viafabric/textures/logo.png", + "mixins": [ + "mixins.viafabric.address.json", + "mixins.viafabric.gui.json", + "mixins.viafabric.debug.json", + "mixins.viafabric.pipeline.json" + ] +} diff --git a/viafabric-mc115/src/main/resources/mixins.viafabric.address.json b/viafabric-mc115/src/main/resources/mixins.viafabric.address.json new file mode 100644 index 0000000..f95ff31 --- /dev/null +++ b/viafabric-mc115/src/main/resources/mixins.viafabric.address.json @@ -0,0 +1,14 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc115.mixin.address.client.MixinConnectScreenThread", + "com.viaversion.fabric.mc115.mixin.address.client.MixinServerAddress", + "com.viaversion.fabric.mc115.mixin.address.client.MixinServerPinger" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/viafabric-mc115/src/main/resources/mixins.viafabric.debug.json b/viafabric-mc115/src/main/resources/mixins.viafabric.debug.json new file mode 100644 index 0000000..c85b7a5 --- /dev/null +++ b/viafabric-mc115/src/main/resources/mixins.viafabric.debug.json @@ -0,0 +1,13 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc115.mixin.debug.client.MixinClientConnectionAccessor", + "com.viaversion.fabric.mc115.mixin.debug.client.MixinDebugHud" + ], + "injectors": { + "defaultRequire": 0 + } +} diff --git a/viafabric-mc115/src/main/resources/mixins.viafabric.gui.json b/viafabric-mc115/src/main/resources/mixins.viafabric.gui.json new file mode 100644 index 0000000..f292a75 --- /dev/null +++ b/viafabric-mc115/src/main/resources/mixins.viafabric.gui.json @@ -0,0 +1,12 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc115.mixin.gui.client.MixinMultiplayerScreen" + ], + "injectors": { + "defaultRequire": 0 + } +} diff --git a/viafabric-mc115/src/main/resources/mixins.viafabric.pipeline.json b/viafabric-mc115/src/main/resources/mixins.viafabric.pipeline.json new file mode 100644 index 0000000..d2cde1a --- /dev/null +++ b/viafabric-mc115/src/main/resources/mixins.viafabric.pipeline.json @@ -0,0 +1,14 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + "com.viaversion.fabric.mc115.mixin.pipeline.MixinClientConnection", + "com.viaversion.fabric.mc115.mixin.pipeline.MixinServerNetworkIoChInit" + ], + "client": [ + "com.viaversion.fabric.mc115.mixin.pipeline.client.MixinClientConnectionChInit" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/viafabric-mc116/build.gradle.kts b/viafabric-mc116/build.gradle.kts index ed6d60b..440d5c6 100644 --- a/viafabric-mc116/build.gradle.kts +++ b/viafabric-mc116/build.gradle.kts @@ -1,11 +1,10 @@ version = rootProject.version dependencies { - minecraft("com.mojang:minecraft:1.14.4") - mappings("net.fabricmc:yarn:1.14.4+build.16:v2") - modImplementation("net.fabricmc:fabric-loader:0.8.2+build.194") + minecraft("com.mojang:minecraft:1.16.5") + mappings("net.fabricmc:yarn:1.16.5+build.6:v2") + modImplementation("net.fabricmc:fabric-loader:0.11.3") - modImplementation("net.fabricmc.fabric-api:fabric-api:0.13.1+build.257-1.14") - modImplementation("io.github.prospector:modmenu:1.7.16.1.14.4+build.128") - modImplementation("io.github.cottonmc:cotton-client-commands:1.0.0+1.15.2") + modImplementation("net.fabricmc.fabric-api:fabric-api:0.32.5+1.16") + modImplementation("com.terraformersmc:modmenu:1.16.9") } \ No newline at end of file diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabric.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabric.java new file mode 100644 index 0000000..1f005d7 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabric.java @@ -0,0 +1,115 @@ +package com.viaversion.fabric.mc116; + +import com.viaversion.fabric.mc116.commands.VRCommandHandler; +import com.viaversion.fabric.mc116.config.VRConfig; +import com.viaversion.fabric.mc116.platform.VRInjector; +import com.viaversion.fabric.mc116.platform.VRLoader; +import com.viaversion.fabric.mc116.platform.VRPlatform; +import com.viaversion.fabric.mc116.protocol.ViaFabricHostnameProtocol; +import com.viaversion.fabric.mc116.util.JLoggerToLog4j; +import com.google.common.collect.Range; +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 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.v1.ClientCommandManager; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.registry.CommandRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.command.CommandSource; +import org.apache.logging.log4j.LogManager; +import com.viaversion.viaversion.ViaManagerImpl; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.data.MappingDataLoader; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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 CompletableFuture INIT_FUTURE = new CompletableFuture<>(); + public static VRConfig 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 String getVersion() { + return FabricLoader.getInstance().getModContainer("viafabric") + .get().getMetadata().getVersion().getFriendlyString(); + } + + public static LiteralArgumentBuilder command(String commandName) { + return LiteralArgumentBuilder.literal(commandName) + .then( + RequiredArgumentBuilder + .argument("args", StringArgumentType.greedyString()) + .executes(((VRCommandHandler) Via.getManager().getCommandHandler())::execute) + .suggests(((VRCommandHandler) Via.getManager().getCommandHandler())::suggestion) + ) + .executes(((VRCommandHandler) Via.getManager().getCommandHandler())::execute); + } + + @Override + public void onInitialize() { + Via.init(ViaManagerImpl.builder() + .injector(new VRInjector()) + .loader(new VRLoader()) + .commandHandler(new VRCommandHandler()) + .platform(new VRPlatform()).build()); + + FabricLoader.getInstance().getModContainer("viabackwards").ifPresent(mod -> MappingDataLoader.enableMappingsCache()); + + ((ViaManagerImpl) Via.getManager()).init(); + + Via.getManager().getProtocolManager().registerBaseProtocol(ViaFabricHostnameProtocol.INSTANCE, Range.lessThan(Integer.MIN_VALUE)); + ProtocolVersion.register(-2, "AUTO"); + + FabricLoader.getInstance().getEntrypoints("viafabric:via_api_initialized", Runnable.class).forEach(Runnable::run); + + try { + registerCommandsV1(); + } catch (NoClassDefFoundError ignored) { + try { + registerCommandsV0(); + JLOGGER.info("Using Fabric Commands V0"); + } catch (NoClassDefFoundError ignored2) { + JLOGGER.info("Couldn't register command as Fabric Commands isn't installed"); + } + } + + config = new VRConfig(FabricLoader.getInstance().getConfigDir().resolve("ViaFabric") + .resolve("viafabric.yml").toFile()); + + INIT_FUTURE.complete(null); + } + + private void registerCommandsV1() { + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("viaversion"))); + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("viaver"))); + CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(command("vvfabric"))); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + ClientCommandManager.DISPATCHER.register(command("viafabricclient")); + } + } + + @SuppressWarnings("deprecation") + private void registerCommandsV0() { + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("viaversion"))); + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("viaver"))); + CommandRegistry.INSTANCE.register(false, dispatcher -> dispatcher.register(command("vvfabric"))); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabricAddress.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabricAddress.java new file mode 100644 index 0000000..40bbab3 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/ViaFabricAddress.java @@ -0,0 +1,77 @@ +package com.viaversion.fabric.mc116; + +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +import java.util.Locale; + +public class ViaFabricAddress { + public int protocol = 0; + public String viaSuffix = null; + public String realAddress = null; + + public ViaFabricAddress parse(String address) { + if (address == null) return null; + String[] parts = address.split("\\."); + + boolean foundDomain = false; + boolean foundOptions = false; + + StringBuilder ourParts = new StringBuilder(); + StringBuilder realAddrBuilder = new StringBuilder(); + + for (int i = parts.length - 1; i >= 0; i--) { + String part = parts[i]; + boolean realAddrPart = false; + if (foundDomain) { + if (!foundOptions) { + if (part.startsWith("_")) { + String arg = part.substring(2); + if (part.toLowerCase(Locale.ROOT).startsWith("_v")) { + try { + protocol = Integer.parseInt(arg); + } catch (NumberFormatException e) { + ProtocolVersion closest = ProtocolVersion.getClosest(arg.replace("_", ".")); + if (closest != null) { + protocol = closest.getVersion(); + } + } + } + } else { + foundOptions = true; + } + } + if (foundOptions) { + realAddrPart = true; + } + } else if (part.equalsIgnoreCase("viafabric")) { + foundDomain = true; + } + if (realAddrPart) { + realAddrBuilder.insert(0, part + "."); + } else { + ourParts.insert(0, part + "."); + } + } + + String realAddr = realAddrBuilder.toString().replaceAll("\\.$", ""); + String suffix = ourParts.toString().replaceAll("\\.$", ""); + + if (realAddr.isEmpty()) { + this.realAddress = address; + } else { + this.realAddress = realAddr; + this.viaSuffix = suffix; + } + + return this; + } + + @Override + public String toString() { + return "ViaFabricAddress{" + + "protocol=" + protocol + + ", viaSuffix='" + viaSuffix + '\'' + + ", realAddress='" + realAddress + '\'' + + '}'; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/NMSCommandSender.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/NMSCommandSender.java new file mode 100644 index 0000000..52c6ba9 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/NMSCommandSender.java @@ -0,0 +1,61 @@ +package com.viaversion.fabric.mc116.commands; + +import com.viaversion.fabric.mc116.platform.VRPlatform; +import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.Entity; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +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); + } + + @Override + public void sendMessage(String s) { + if (source instanceof ServerCommandSource) { + ((ServerCommandSource) source).sendFeedback(Text.Serializer.fromJson(VRPlatform.legacyToJson(s)), false); + } else if (source instanceof FabricClientCommandSource) { + ((FabricClientCommandSource) source).sendFeedback(Text.Serializer.fromJson(VRPlatform.legacyToJson(s))); + } + } + + private String legacyToJson(String legacy) { + return GsonComponentSerializer.gson().serialize(LegacyComponentSerializer.legacySection().deserialize(legacy)); + } + + @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.fromString(getName()); + } + + @Override + public String getName() { + if (source instanceof ServerCommandSource) { + return ((ServerCommandSource) source).getName(); + } else if (source instanceof FabricClientCommandSource) { + return ((FabricClientCommandSource) source).getPlayer().getEntityName(); + } + return "?"; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/UserCommandSender.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/UserCommandSender.java new file mode 100644 index 0000000..e2e5609 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/UserCommandSender.java @@ -0,0 +1,35 @@ +package com.viaversion.fabric.mc116.commands; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.connection.UserConnection; + +import java.util.UUID; + +public class UserCommandSender implements ViaCommandSender { + private UserConnection con; + + public UserCommandSender(UserConnection con) { + this.con = con; + } + + @Override + public boolean hasPermission(String s) { + return false; + } + + @Override + public void sendMessage(String s) { + Via.getPlatform().sendMessage(getUUID(), s); + } + + @Override + public UUID getUUID() { + return con.getProtocolInfo().getUuid(); + } + + @Override + public String getName() { + return con.getProtocolInfo().getUsername(); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/VRCommandHandler.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/VRCommandHandler.java new file mode 100644 index 0000000..e1ce488 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/VRCommandHandler.java @@ -0,0 +1,55 @@ +package com.viaversion.fabric.mc116.commands; + +import com.viaversion.fabric.mc116.commands.subs.LeakDetectSubCommand; +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 net.minecraft.command.CommandSource; +import com.viaversion.viaversion.commands.ViaCommandHandler; + +import java.util.concurrent.CompletableFuture; + +public class VRCommandHandler extends ViaCommandHandler { + { + try { + registerSubCommand(new LeakDetectSubCommand()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public int execute(CommandContext 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 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-mc116/src/main/java/com/viaversion/fabric/mc116/commands/subs/LeakDetectSubCommand.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/subs/LeakDetectSubCommand.java new file mode 100644 index 0000000..1a959e0 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/commands/subs/LeakDetectSubCommand.java @@ -0,0 +1,48 @@ +package com.viaversion.fabric.mc116.commands.subs; + +import io.netty.util.ResourceLeakDetector; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.command.ViaSubCommand; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class LeakDetectSubCommand extends ViaSubCommand { + @Override + public String name() { + return "leakdetect"; + } + + @Override + public String description() { + return "Sets ResourceLeakDetector level"; + } + + @Override + public boolean execute(ViaCommandSender viaCommandSender, String[] strings) { + if (strings.length == 1) { + try { + ResourceLeakDetector.Level level = ResourceLeakDetector.Level.valueOf(strings[0]); + ResourceLeakDetector.setLevel(level); + viaCommandSender.sendMessage("Set leak detector level to " + level); + } catch (IllegalArgumentException e) { + viaCommandSender.sendMessage("Invalid level (" + Arrays.toString(ResourceLeakDetector.Level.values()) + ")"); + } + } else { + viaCommandSender.sendMessage("Current leak detection level is " + ResourceLeakDetector.getLevel()); + } + return true; + } + + @Override + public List onTabComplete(ViaCommandSender sender, String[] args) { + if (args.length == 1) { + return Arrays.stream(ResourceLeakDetector.Level.values()) + .map(Enum::name) + .filter(it -> it.startsWith(args[0])) + .collect(Collectors.toList()); + } + return super.onTabComplete(sender, args); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/config/VRConfig.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/config/VRConfig.java new file mode 100644 index 0000000..9408131 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/config/VRConfig.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc116.config; + +import com.viaversion.viaversion.util.Config; + +import java.io.File; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VRConfig extends Config { + public static final String ENABLE_CLIENT_SIDE = "enable-client-side"; + public static final String CLIENT_SIDE_VERSION = "client-side-version"; + public static final String CLIENT_SIDE_FORCE_DISABLE = "client-side-force-disable"; + public static final String HIDE_BUTTON = "hide-button"; + + public VRConfig(File configFile) { + super(configFile); + reloadConfig(); + } + + @Override + public URL getDefaultConfigURL() { + return getClass().getClassLoader().getResource("assets/viafabric/config.yml"); + } + + @Override + protected void handleConfig(Map map) { + } + + @Override + public List getUnsupportedOptions() { + return Collections.emptyList(); + } + + public boolean isClientSideEnabled() { + return getBoolean(ENABLE_CLIENT_SIDE, false); + } + + public void setClientSideEnabled(boolean val) { + set(ENABLE_CLIENT_SIDE, val); + } + + public int getClientSideVersion() { + return getInt(CLIENT_SIDE_VERSION, -1); + } + + public void setClientSideVersion(int val) { + set(CLIENT_SIDE_VERSION, val); + } + + public Collection getClientSideForceDisable() { + return (List) get(CLIENT_SIDE_FORCE_DISABLE, List.class, Collections.emptyList()); + } + + public void setHideButton(boolean val) { + set(HIDE_BUTTON, val); + } + + public boolean isHideButton() { + return getBoolean(HIDE_BUTTON, false); + } + + public boolean isForcedDisable(String line) { + return getClientSideForceDisable().contains(line); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ModMenuConfig.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ModMenuConfig.java new file mode 100644 index 0000000..0b6185d --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ModMenuConfig.java @@ -0,0 +1,11 @@ +package com.viaversion.fabric.mc116.gui; + +import io.github.prospector.modmenu.api.ConfigScreenFactory; +import io.github.prospector.modmenu.api.ModMenuApi; + +public class ModMenuConfig implements ModMenuApi { + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return ViaConfigScreen::new; + } +} \ No newline at end of file diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ViaConfigScreen.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ViaConfigScreen.java new file mode 100644 index 0000000..2cf23d6 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/gui/ViaConfigScreen.java @@ -0,0 +1,171 @@ +package com.viaversion.fabric.mc116.gui; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.util.ProtocolUtils; +import com.viaversion.viaversion.api.Via; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ConfirmScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ScreenTexts; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.TranslatableText; + +import java.util.concurrent.CompletableFuture; + +@Environment(EnvType.CLIENT) +public class ViaConfigScreen extends Screen { + private static CompletableFuture latestProtocolSave; + private final Screen parent; + private TextFieldWidget protocolVersion; + + public ViaConfigScreen(Screen parent) { + super(new TranslatableText("gui.viafabric_config.title")); + this.parent = parent; + } + + private static int getProtocolTextColor(boolean valid, boolean supported) { + + if (!valid) { + return 0xff0000; // Red + } else if (!supported) { + return 0xFFA500; // Orange + } + return 0xE0E0E0; // Default + } + + @Override + protected void init() { + int entries = 0; + + this.addButton(new ButtonWidget(this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, getClientSideText(), this::onClickClientSide)); + entries++; + + this.addButton(new ButtonWidget(this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, getHideViaButtonText(), this::onHideViaButton)); + entries++; + + protocolVersion = new TextFieldWidget(this.textRenderer, + this.width / 2 - 155 + entries % 2 * 160, + this.height / 6 + 24 * (entries >> 1), + 150, + 20, new TranslatableText("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.children.add(protocolVersion); + + //noinspection ConstantConditions + if (entries % 2 == 1) { + entries++; + } + + this.addButton(new ButtonWidget(this.width / 2 - 100, this.height / 6 + 24 * (entries >> 1), 200, 20, ScreenTexts.DONE, (buttonWidget) -> this.client.openScreen(this.parent))); + } + + 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(ProtocolUtils.isSupported(newVersion, Via.getAPI().getServerVersion().lowestSupportedVersion()), + validProtocol)); + + int finalNewVersion = newVersion; + if (latestProtocolSave == null) latestProtocolSave = CompletableFuture.completedFuture(null); + ViaFabric.config.setClientSideVersion(finalNewVersion); + latestProtocolSave = latestProtocolSave.thenRunAsync(ViaFabric.config::saveConfig, ViaFabric.ASYNC_EXECUTOR); + } + + private void onClickClientSide(ButtonWidget widget) { + if (!ViaFabric.config.isClientSideEnabled()) { + MinecraftClient.getInstance().openScreen(new ConfirmScreen( + answer -> { + if (answer) { + ViaFabric.config.setClientSideEnabled(true); + ViaFabric.config.setClientSideVersion(-2); // AUTO + ViaFabric.config.saveConfig(); + widget.setMessage(getClientSideText()); + } + MinecraftClient.getInstance().openScreen(this); + }, + new TranslatableText("gui.enable_client_side.question"), + new TranslatableText("gui.enable_client_side.warning"), + new TranslatableText("gui.enable_client_side.enable"), + new TranslatableText("gui.cancel") + )); + } else { + ViaFabric.config.setClientSideEnabled(false); + ViaFabric.config.saveConfig(); + } + widget.setMessage(getClientSideText()); + } + + @Override + public void removed() { + ViaFabric.config.saveConfig(); + } + + @Override + public void onClose() { + this.client.openScreen(this.parent); + } + + private TranslatableText getClientSideText() { + return ViaFabric.config.isClientSideEnabled() ? + new TranslatableText("gui.client_side.disable") + : new TranslatableText("gui.client_side.enable"); + } + + private TranslatableText getHideViaButtonText() { + return ViaFabric.config.isHideButton() ? + new TranslatableText("gui.hide_via_button.disable") : new TranslatableText("gui.hide_via_button.enable"); + } + + private void onHideViaButton(ButtonWidget widget) { + ViaFabric.config.setHideButton(!ViaFabric.config.isHideButton()); + ViaFabric.config.saveConfig(); + widget.setMessage(getHideViaButtonText()); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + this.renderBackground(matrices); + drawCenteredText(matrices, this.textRenderer, this.title, this.width / 2, 20, 16777215); + super.render(matrices, mouseX, mouseY, delta); + protocolVersion.render(matrices, mouseX, mouseY, delta); + } + + @Override + public void tick() { + super.tick(); + protocolVersion.tick(); + } +} + diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/CommonTransformer.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/CommonTransformer.java new file mode 100644 index 0000000..c6128c5 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/CommonTransformer.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc116.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.handler.codec.MessageToMessageDecoder; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; + +public class CommonTransformer { + public static final String HANDLER_DECODER_NAME = "via-decoder"; + public static final String HANDLER_ENCODER_NAME = "via-encoder"; + + public static void decompress(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + ChannelHandler handler = ctx.pipeline().get("decompress"); + ByteBuf decompressed = handler instanceof MessageToMessageDecoder + ? (ByteBuf) PipelineUtil.callDecode((MessageToMessageDecoder) handler, ctx, buf).get(0) + : (ByteBuf) PipelineUtil.callDecode((ByteToMessageDecoder) handler, ctx, buf).get(0); + try { + buf.clear().writeBytes(decompressed); + } finally { + decompressed.release(); + } + } + + public static void compress(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + ByteBuf compressed = ctx.alloc().buffer(); + try { + PipelineUtil.callEncode((MessageToByteEncoder) ctx.pipeline().get("compress"), ctx, buf, compressed); + buf.clear().writeBytes(compressed); + } finally { + compressed.release(); + } + } +} \ No newline at end of file diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricDecodeHandler.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricDecodeHandler.java new file mode 100644 index 0000000..5f5a8cc --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricDecodeHandler.java @@ -0,0 +1,85 @@ +package com.viaversion.fabric.mc116.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.exception.CancelCodecException; +import com.viaversion.viaversion.exception.CancelDecoderException; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +@ChannelHandler.Sharable +public class FabricDecodeHandler extends MessageToMessageDecoder { + private final UserConnection info; + private boolean handledCompression; + private boolean skipDoubleTransform; + + public FabricDecodeHandler(UserConnection info) { + this.info = info; + } + + public UserConnection getInfo() { + return info; + } + + // https://github.com/ViaVersion/ViaVersion/blob/master/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityDecodeHandler.java + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + if (skipDoubleTransform) { + skipDoubleTransform = false; + out.add(bytebuf.retain()); + return; + } + + if (!info.checkIncomingPacket()) throw CancelDecoderException.generate(null); + if (!info.shouldTransformPacket()) { + out.add(bytebuf.retain()); + return; + } + + ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(bytebuf); + try { + boolean needsCompress = handleCompressionOrder(ctx, transformedBuf); + + info.transformIncoming(transformedBuf, CancelDecoderException::generate); + + if (needsCompress) { + CommonTransformer.compress(ctx, transformedBuf); + skipDoubleTransform = true; + } + out.add(transformedBuf.retain()); + } finally { + transformedBuf.release(); + } + } + + private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + if (handledCompression) return false; + + int decoderIndex = ctx.pipeline().names().indexOf("decompress"); + if (decoderIndex == -1) return false; + handledCompression = true; + if (decoderIndex > ctx.pipeline().names().indexOf("via-decoder")) { + // Need to decompress this packet due to bad order + CommonTransformer.decompress(ctx, buf); + ChannelHandler encoder = ctx.pipeline().get("via-encoder"); + ChannelHandler decoder = ctx.pipeline().get("via-decoder"); + ctx.pipeline().remove(encoder); + ctx.pipeline().remove(decoder); + ctx.pipeline().addAfter("compress", "via-encoder", encoder); + ctx.pipeline().addAfter("decompress", "via-decoder", decoder); + return true; + } + return false; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelCodecException.class)) return; + super.exceptionCaught(ctx, cause); + } +} \ No newline at end of file diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricEncodeHandler.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricEncodeHandler.java new file mode 100644 index 0000000..f90878d --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/FabricEncodeHandler.java @@ -0,0 +1,72 @@ +package com.viaversion.fabric.mc116.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.exception.CancelCodecException; +import com.viaversion.viaversion.exception.CancelEncoderException; +import com.viaversion.viaversion.util.PipelineUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +@ChannelHandler.Sharable +public class FabricEncodeHandler extends MessageToMessageEncoder { + private final UserConnection info; + private boolean handledCompression; + + public FabricEncodeHandler(UserConnection info) { + this.info = info; + } + + @Override + protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null); + if (!info.shouldTransformPacket()) { + out.add(bytebuf.retain()); + return; + } + + ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(bytebuf); + try { + boolean needsCompress = handleCompressionOrder(ctx, transformedBuf); + + info.transformOutgoing(transformedBuf, CancelEncoderException::generate); + + if (needsCompress) { + CommonTransformer.compress(ctx, transformedBuf); + } + out.add(transformedBuf.retain()); + } finally { + transformedBuf.release(); + } + } + + private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException { + if (handledCompression) return false; + + int encoderIndex = ctx.pipeline().names().indexOf("compress"); + if (encoderIndex == -1) return false; + handledCompression = true; + if (encoderIndex > ctx.pipeline().names().indexOf("via-encoder")) { + // Need to decompress this packet due to bad order + CommonTransformer.decompress(ctx, buf); + ChannelHandler encoder = ctx.pipeline().get("via-encoder"); + ChannelHandler decoder = ctx.pipeline().get("via-decoder"); + ctx.pipeline().remove(encoder); + ctx.pipeline().remove(decoder); + ctx.pipeline().addAfter("compress", "via-encoder", encoder); + ctx.pipeline().addAfter("decompress", "via-decoder", decoder); + return true; + } + return false; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelCodecException.class)) return; + super.exceptionCaught(ctx, cause); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/clientside/ProtocolDetectionHandler.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/clientside/ProtocolDetectionHandler.java new file mode 100644 index 0000000..a875d03 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/handler/clientside/ProtocolDetectionHandler.java @@ -0,0 +1,79 @@ +package com.viaversion.fabric.mc116.handler.clientside; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.service.ProtocolAutoDetector; +import com.viaversion.viaversion.util.Pair; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; + +import java.net.InetSocketAddress; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class ProtocolDetectionHandler extends ChannelDuplexHandler { + private final Queue> queuedMessages = new ArrayDeque<>(); + private boolean hold = true; + private boolean pendentFlush; + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + if (ctx.channel().remoteAddress() instanceof InetSocketAddress) { + ScheduledFuture timeoutRun = ctx.executor().schedule(() -> { + ViaFabric.JLOGGER.warning("Timeout for protocol auto-detection in " + + ctx.channel().remoteAddress() + " server"); + hold = false; + drainQueue(ctx); + ctx.pipeline().remove(this); + }, 10, TimeUnit.SECONDS); + ProtocolAutoDetector.detectVersion(((InetSocketAddress) ctx.channel().remoteAddress())) + .whenComplete((obj, ex) -> { + ctx.pipeline().remove(this); + timeoutRun.cancel(false); + }); + // Let's cache it before we need it + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!hold) { + drainQueue(ctx); + super.write(ctx, msg, promise); + } else { + queuedMessages.add(new Pair<>(msg, promise)); + } + } + + @Override + public void flush(ChannelHandlerContext ctx) throws Exception { + if (!hold) { + drainQueue(ctx); + super.flush(ctx); + } else { + pendentFlush = true; + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + drainQueue(ctx); + super.channelInactive(ctx); + } + + private void drainQueue(ChannelHandlerContext ctx) { + queuedMessages.forEach(it -> ctx.write(it.getKey(), it.getValue())); + queuedMessages.clear(); + if (pendentFlush) ctx.flush(); + pendentFlush = false; + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + drainQueue(ctx); + super.handlerRemoved(ctx); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinConnectScreenThread.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinConnectScreenThread.java new file mode 100644 index 0000000..cdd00cc --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinConnectScreenThread.java @@ -0,0 +1,24 @@ +package com.viaversion.fabric.mc116.mixin.address.client; + +import com.viaversion.fabric.mc116.ViaFabricAddress; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +@Mixin(targets = "net/minecraft/client/gui/screen/ConnectScreen$1", priority = 2000) +public class MixinConnectScreenThread { + @Redirect(method = "run()V", at = @At(value = "INVOKE", + target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;")) + private InetAddress resolveViaFabricAddr(String address) throws UnknownHostException { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return InetAddress.getByName(address); + } + + InetAddress resolved = InetAddress.getByName(viaAddr.realAddress); + return InetAddress.getByAddress(resolved.getHostName() + "." + viaAddr.viaSuffix, resolved.getAddress()); + } +} \ No newline at end of file diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerAddress.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerAddress.java new file mode 100644 index 0000000..c992255 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerAddress.java @@ -0,0 +1,27 @@ +package com.viaversion.fabric.mc116.mixin.address.client; + +import com.viaversion.fabric.mc116.ViaFabricAddress; +import com.mojang.datafixers.util.Pair; +import net.minecraft.network.ServerAddress; +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.Redirect; + +@Mixin(ServerAddress.class) +public abstract class MixinServerAddress { + @Shadow + private static Pair resolveServer(String address) { + throw new AssertionError(); + } + + @Redirect(method = "parse", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ServerAddress;resolveServer(Ljava/lang/String;)Lcom/mojang/datafixers/util/Pair;")) + private static Pair modifySrvAddr(String address) { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return resolveServer(address); + } + + return resolveServer(viaAddr.realAddress).mapFirst(it -> it.replaceAll("\\.$", "") + "." + viaAddr.viaSuffix); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerPinger.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerPinger.java new file mode 100644 index 0000000..6060b28 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/address/client/MixinServerPinger.java @@ -0,0 +1,25 @@ +package com.viaversion.fabric.mc116.mixin.address.client; + +import com.viaversion.fabric.mc116.ViaFabricAddress; +import net.minecraft.client.network.MultiplayerServerListPinger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +@Mixin(MultiplayerServerListPinger.class) +public class MixinServerPinger { + @Redirect(method = "add", at = @At(value = "INVOKE", + target = "Ljava/net/InetAddress;getByName(Ljava/lang/String;)Ljava/net/InetAddress;")) + private InetAddress resolveViaFabricAddr(String address) throws UnknownHostException { + ViaFabricAddress viaAddr = new ViaFabricAddress().parse(address); + if (viaAddr.viaSuffix == null) { + return InetAddress.getByName(address); + } + + InetAddress resolved = InetAddress.getByName(viaAddr.realAddress); + return InetAddress.getByAddress(resolved.getHostName() + "." + viaAddr.viaSuffix, resolved.getAddress()); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinClientConnectionAccessor.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinClientConnectionAccessor.java new file mode 100644 index 0000000..6c22b92 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinClientConnectionAccessor.java @@ -0,0 +1,12 @@ +package com.viaversion.fabric.mc116.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-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinDebugHud.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinDebugHud.java new file mode 100644 index 0000000..5e6ecee --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/debug/client/MixinDebugHud.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc116.mixin.debug.client; + +import com.viaversion.fabric.mc116.handler.CommonTransformer; +import com.viaversion.fabric.mc116.handler.FabricDecodeHandler; +import com.viaversion.viaversion.api.connection.ProtocolInfo; +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 com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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) { + ProtocolInfo protocol = ((FabricDecodeHandler) viaDecoder).getInfo().getProtocolInfo(); + if (protocol != null) { + ProtocolVersion serverVer = ProtocolVersion.getProtocol(protocol.getServerProtocolVersion()); + ProtocolVersion clientVer = ProtocolVersion.getProtocol(protocol.getProtocolVersion()); + line += " / C: " + clientVer.getName() + " (" + clientVer.getVersion() + ") S: " + + serverVer.getName() + " (" + serverVer.getVersion() + ") A: " + protocol.getUser().isActive(); + } + } + + info.getReturnValue().add(line); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/gui/client/MixinMultiplayerScreen.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/gui/client/MixinMultiplayerScreen.java new file mode 100644 index 0000000..cf35495 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/gui/client/MixinMultiplayerScreen.java @@ -0,0 +1,38 @@ +package com.viaversion.fabric.mc116.mixin.gui.client; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.gui.ViaConfigScreen; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +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.text.TranslatableText; +import net.minecraft.util.Identifier; +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(MultiplayerScreen.class) +public abstract class MixinMultiplayerScreen extends Screen { + protected MixinMultiplayerScreen(Text title, UnsupportedOperationException e) { + super(title); + throw e; + } + + @Inject(method = "init", at = @At("TAIL")) + private void onInit(CallbackInfo ci) { + ButtonWidget enableClientSideViaVersion = new TexturedButtonWidget(this.width / 2 + 113, 10, + 40, 20, // Size + 0, 0, // Start pos of texture + 20, // v Hover offset + new Identifier("viafabric:textures/gui/widgets.png"), + 256, 256, // Texture size + it -> MinecraftClient.getInstance().openScreen(new ViaConfigScreen(this)), + new TranslatableText("gui.via_button")); + if (ViaFabric.config.isHideButton()) enableClientSideViaVersion.visible = false; + addButton(enableClientSideViaVersion); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinClientConnection.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinClientConnection.java new file mode 100644 index 0000000..7064550 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinClientConnection.java @@ -0,0 +1,31 @@ +package com.viaversion.fabric.mc116.mixin.pipeline; + +import io.netty.channel.Channel; +import net.minecraft.network.ClientConnection; +import org.apache.logging.log4j.Logger; +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.Redirect; + + +@Mixin(ClientConnection.class) +public class MixinClientConnection { + @Shadow + private Channel channel; + + @Redirect( + method = "exceptionCaught", + remap = false, + at = @At( + value = "INVOKE", + target = "Lorg/apache/logging/log4j/Logger;debug(Ljava/lang/String;Ljava/lang/Throwable;)V" + )) + private void redirectDebug(Logger logger, String message, Throwable t) { + if ("Failed to sent packet".equals(message)) { + logger.info(message, t); + } else { + logger.debug(message, t); + } + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinServerNetworkIoChInit.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinServerNetworkIoChInit.java new file mode 100644 index 0000000..f00b137 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/MixinServerNetworkIoChInit.java @@ -0,0 +1,28 @@ +package com.viaversion.fabric.mc116.mixin.pipeline; + +import com.viaversion.fabric.mc116.handler.CommonTransformer; +import com.viaversion.fabric.mc116.handler.FabricDecodeHandler; +import com.viaversion.fabric.mc116.handler.FabricEncodeHandler; +import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; +import io.netty.channel.Channel; +import io.netty.channel.socket.SocketChannel; +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; +import com.viaversion.viaversion.api.connection.UserConnection; + +@Mixin(targets = "net.minecraft.server.ServerNetworkIo$1") +public class MixinServerNetworkIoChInit { + @Inject(method = "initChannel", at = @At(value = "TAIL"), remap = false) + private void onInitChannel(Channel channel, CallbackInfo ci) { + if (channel instanceof SocketChannel) { + UserConnection user = new UserConnectionImpl(channel); + new ProtocolPipelineImpl(user); + + channel.pipeline().addBefore("encoder", CommonTransformer.HANDLER_ENCODER_NAME, new FabricEncodeHandler(user)); + channel.pipeline().addBefore("decoder", CommonTransformer.HANDLER_DECODER_NAME, new FabricDecodeHandler(user)); + } + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/client/MixinClientConnectionChInit.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/client/MixinClientConnectionChInit.java new file mode 100644 index 0000000..af60422 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/mixin/pipeline/client/MixinClientConnectionChInit.java @@ -0,0 +1,35 @@ +package com.viaversion.fabric.mc116.mixin.pipeline.client; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.handler.CommonTransformer; +import com.viaversion.fabric.mc116.handler.FabricDecodeHandler; +import com.viaversion.fabric.mc116.handler.FabricEncodeHandler; +import com.viaversion.fabric.mc116.handler.clientside.ProtocolDetectionHandler; +import com.viaversion.fabric.mc116.protocol.ViaFabricHostnameProtocol; +import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; +import io.netty.channel.Channel; +import io.netty.channel.socket.SocketChannel; +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; +import com.viaversion.viaversion.api.connection.UserConnection; + +@Mixin(targets = "net.minecraft.network.ClientConnection$1") +public class MixinClientConnectionChInit { + @Inject(method = "initChannel", at = @At(value = "TAIL"), remap = false) + private void onInitChannel(Channel channel, CallbackInfo ci) { + if (channel instanceof SocketChannel) { + UserConnection user = new UserConnectionImpl(channel, true); + new ProtocolPipelineImpl(user).add(ViaFabricHostnameProtocol.INSTANCE); + + channel.pipeline() + .addBefore("encoder", CommonTransformer.HANDLER_ENCODER_NAME, new FabricEncodeHandler(user)) + .addBefore("decoder", CommonTransformer.HANDLER_DECODER_NAME, new FabricDecodeHandler(user)); + if (ViaFabric.config.isClientSideEnabled()) { + channel.pipeline().addAfter(CommonTransformer.HANDLER_ENCODER_NAME, "via-autoprotocol", new ProtocolDetectionHandler()); + } + } + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRInjector.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRInjector.java new file mode 100644 index 0000000..942ecf8 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRInjector.java @@ -0,0 +1,61 @@ +package com.viaversion.fabric.mc116.platform; + +import com.viaversion.fabric.mc116.handler.CommonTransformer; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import com.viaversion.viaversion.api.platform.ViaInjector; +import com.viaversion.viaversion.util.GsonUtil; +import com.viaversion.viaversion.libs.gson.JsonObject; + +import java.lang.reflect.Method; +import java.util.Arrays; + +public class VRInjector implements ViaInjector { + @Override + public void inject() { + // *looks at Mixins* + } + + @Override + public void uninject() { + // not possible *plays sad violin* + } + + @Override + public int getServerProtocolVersion() { + return SharedConstants.getGameVersion().getProtocolVersion(); + } + + @Override + public String getEncoderName() { + return CommonTransformer.HANDLER_ENCODER_NAME; + } + + @Override + public String getDecoderName() { + return CommonTransformer.HANDLER_DECODER_NAME; + } + + @Override + public JsonObject getDump() { + JsonObject obj = new JsonObject(); + try { + obj.add("serverNetworkIOChInit", GsonUtil.getGson().toJsonTree( + Arrays.stream(Class.forName("net.minecraft.class_3242$1").getDeclaredMethods()) + .map(Method::toString) + .toArray(String[]::new))); + } catch (ClassNotFoundException ignored) { + } + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + try { + obj.add("clientConnectionChInit", GsonUtil.getGson().toJsonTree( + Arrays.stream(Class.forName("net.minecraft.class_2535$1").getDeclaredMethods()) + .map(Method::toString) + .toArray(String[]::new))); + } catch (ClassNotFoundException ignored) { + } + } + return obj; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRLoader.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRLoader.java new file mode 100644 index 0000000..67f5176 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRLoader.java @@ -0,0 +1,34 @@ +package com.viaversion.fabric.mc116.platform; + +import com.viaversion.fabric.mc116.providers.VRHandItemProvider; +import com.viaversion.fabric.mc116.providers.VRVersionProvider; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.platform.ViaPlatformLoader; +import com.viaversion.viaversion.bungee.providers.BungeeMovementTransmitter; +import com.viaversion.viaversion.api.protocol.version.VersionProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider; + +public class VRLoader implements ViaPlatformLoader { + @Override + public void load() { + Via.getManager().getProviders().use(MovementTransmitterProvider.class, new BungeeMovementTransmitter()); + Via.getManager().getProviders().use(VersionProvider.class, new VRVersionProvider()); + + if (Via.getPlatform().getConf().isItemCache()) { + VRHandItemProvider handProvider = new VRHandItemProvider(); + if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { + handProvider.registerClientTick(); + } + handProvider.registerServerTick(); + Via.getManager().getProviders().use(HandItemProvider.class, handProvider); + } + } + + @Override + public void unload() { + // Nothing to do + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRPlatform.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRPlatform.java new file mode 100644 index 0000000..d42e5e6 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRPlatform.java @@ -0,0 +1,273 @@ +package com.viaversion.fabric.mc116.platform; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.commands.NMSCommandSender; +import com.viaversion.fabric.mc116.commands.UserCommandSender; +import com.viaversion.fabric.mc116.util.FutureTaskId; +import com.viaversion.fabric.mc116.util.JLoggerToLog4j; +import com.viaversion.viaversion.api.configuration.ViaVersionConfig; +import com.viaversion.viaversion.api.platform.PlatformTask; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import org.apache.logging.log4j.LogManager; +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.ViaAPI; +import com.viaversion.viaversion.api.command.ViaCommandSender; +import com.viaversion.viaversion.api.configuration.ConfigurationProvider; +import com.viaversion.viaversion.api.platform.ViaPlatform; +import com.viaversion.viaversion.dump.PluginInfo; +import com.viaversion.viaversion.util.GsonUtil; +import com.viaversion.viaversion.libs.gson.JsonObject; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import com.viaversion.viaversion.libs.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class VRPlatform implements ViaPlatform { + private final Logger logger = new JLoggerToLog4j(LogManager.getLogger("ViaVersion")); + private final VRViaConfig config; + private final File dataFolder; + private final ViaAPI api; + + public VRPlatform() { + Path configDir = FabricLoader.getInstance().getConfigDirectory().toPath().resolve("ViaFabric"); + config = new VRViaConfig(configDir.resolve("viaversion.yml").toFile()); + dataFolder = configDir.toFile(); + api = new VRViaAPI(); + } + + 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(); + } + + public static String legacyToJson(String legacy) { + return GsonComponentSerializer.gson().serialize(LegacyComponentSerializer.legacySection().deserialize(legacy)); + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public String getPlatformName() { + return "ViaFabric"; + } + + @Override + public String getPlatformVersion() { + return ViaFabric.getVersion(); + } + + @Override + public String getPluginVersion() { + return FabricLoader.getInstance().getModContainer("viaversion").map(ModContainer::getMetadata) + .map(ModMetadata::getVersion).map(Version::getFriendlyString).orElse("UNKNOWN"); + } + + @Override + public FutureTaskId runAsync(Runnable runnable) { + return new FutureTaskId(CompletableFuture + .runAsync(runnable, ViaFabric.ASYNC_EXECUTOR) + .exceptionally(throwable -> { + if (!(throwable instanceof CancellationException)) { + throwable.printStackTrace(); + } + return null; + }) + ); + } + + @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())); + } + + private FutureTaskId runEventLoop(Runnable runnable) { + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .submit(runnable) + .addListener(errorLogger()) + ); + } + + @Override + public PlatformTask runSync(Runnable runnable, Long ticks) { + // ViaVersion seems to not need to run delayed tasks on main thread + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .schedule(() -> runSync(runnable), ticks * 50, TimeUnit.MILLISECONDS) + .addListener(errorLogger()) + ); + } + + @Override + public PlatformTask runRepeatingSync(Runnable runnable, Long ticks) { + // ViaVersion seems to not need to run repeating tasks on main thread + return new FutureTaskId( + ViaFabric.EVENT_LOOP + .scheduleAtFixedRate(() -> runSync(runnable), 0, ticks * 50, TimeUnit.MILLISECONDS) + .addListener(errorLogger()) + ); + } + + private > GenericFutureListener errorLogger() { + return future -> { + if (!future.isCancelled() && future.cause() != null) { + future.cause().printStackTrace(); + } + }; + } + + @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(Text.Serializer.fromJson( + legacyToJson(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(Text.Serializer.fromJson(legacyToJson(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 + public boolean isPluginEnabled() { + return true; + } + + @Override + public ViaAPI getApi() { + return api; + } + + @Override + public ViaVersionConfig getConf() { + return config; + } + + @Override + public ConfigurationProvider getConfigurationProvider() { + return config; + } + + @Override + public File getDataFolder() { + return dataFolder; + } + + @Override + public void onReload() { + // Nothing to do + } + + @Override + public JsonObject getDump() { + JsonObject platformSpecific = new JsonObject(); + List mods = new ArrayList<>(); + for (ModContainer mod : FabricLoader.getInstance().getAllMods()) { + mods.add(new PluginInfo(true, + mod.getMetadata().getId() + " (" + mod.getMetadata().getName() + ")", + mod.getMetadata().getVersion().getFriendlyString(), + null, + mod.getMetadata().getAuthors().stream() + .map(info -> info.getName() + + (info.getContact().asMap().isEmpty() ? "" : " " + info.getContact().asMap())) + .collect(Collectors.toList()) + )); + } + + platformSpecific.add("mods", GsonUtil.getGson().toJsonTree(mods)); + return platformSpecific; + } + + @Override + public boolean isOldClientsAllowed() { + return true; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaAPI.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaAPI.java new file mode 100644 index 0000000..aebbe40 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaAPI.java @@ -0,0 +1,8 @@ +package com.viaversion.fabric.mc116.platform; + +import com.viaversion.viaversion.ViaAPIBase; + +import java.util.UUID; + +public class VRViaAPI extends ViaAPIBase { +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaConfig.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaConfig.java new file mode 100644 index 0000000..7984e9b --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/platform/VRViaConfig.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc116.platform; + +import com.viaversion.viaversion.configuration.AbstractViaConfig; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class VRViaConfig extends AbstractViaConfig { + // Based on Sponge ViaVersion + private static List UNSUPPORTED = Arrays.asList("anti-xray-patch", "bungee-ping-interval", + "bungee-ping-save", "bungee-servers", "quick-move-action-fix", "nms-player-ticking", + "velocity-ping-interval", "velocity-ping-save", "velocity-servers", + "blockconnection-method", "change-1_9-hitbox", "change-1_14-hitbox"); + + public VRViaConfig(File configFile) { + super(configFile); + // Load config + reloadConfig(); + } + + @Override + public URL getDefaultConfigURL() { + return getClass().getClassLoader().getResource("assets/viaversion/config.yml"); + } + + @Override + protected void handleConfig(Map config) { + // Nothing Currently + } + + @Override + public List getUnsupportedOptions() { + return UNSUPPORTED; + } + + @Override + public boolean isAntiXRay() { + return false; + } + + @Override + public boolean isNMSPlayerTicking() { + return false; + } + + @Override + public boolean is1_12QuickMoveActionFix() { + return false; + } + + @Override + public String getBlockConnectionMethod() { + return "packet"; + } + + @Override + public boolean is1_9HitboxFix() { + return false; + } + + @Override + public boolean is1_14HitboxFix() { + return false; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/protocol/ViaFabricHostnameProtocol.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/protocol/ViaFabricHostnameProtocol.java new file mode 100644 index 0000000..1e38219 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/protocol/ViaFabricHostnameProtocol.java @@ -0,0 +1,34 @@ +package com.viaversion.fabric.mc116.protocol; + +import com.viaversion.fabric.mc116.ViaFabricAddress; +import com.viaversion.viaversion.api.protocol.AbstractSimpleProtocol; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; +import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.api.protocol.packet.State; + +public class ViaFabricHostnameProtocol extends AbstractSimpleProtocol { + public static final ViaFabricHostnameProtocol INSTANCE = new ViaFabricHostnameProtocol(); + + @Override + protected void registerPackets() { + registerServerbound(State.HANDSHAKE, 0, 0, new PacketRemapper() { + @Override + public void registerMap() { + map(Type.VAR_INT); // Protocol version + map(Type.STRING, new ValueTransformer(Type.STRING) { + @Override + public String transform(PacketWrapper packetWrapper, String s) { + return new ViaFabricAddress().parse(s).realAddress; + } + }); + } + }); + } + + @Override + public boolean isBaseProtocol() { + return true; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRHandItemProvider.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRHandItemProvider.java new file mode 100644 index 0000000..9dc6714 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRHandItemProvider.java @@ -0,0 +1,110 @@ +package com.viaversion.fabric.mc116.providers; + +import com.viaversion.fabric.mc116.ViaFabric; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.world.WorldTickCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.item.Item; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class VRHandItemProvider extends HandItemProvider { + public Item clientItem = null; + public Map serverPlayers = new ConcurrentHashMap<>(); + + @Override + public Item getHandItem(UserConnection info) { + Item serverItem; + if (info.isClientSide()) { + return getClientItem(); + } else if ((serverItem = serverPlayers.get(info.getProtocolInfo().getUuid())) != null) { + return new Item(serverItem); + } + return super.getHandItem(info); + } + + private Item getClientItem() { + if (clientItem == null) { + return new Item(0, (byte) 0, (short) 0, null); + } + return new Item(clientItem); + } + + @Environment(EnvType.CLIENT) + public void registerClientTick() { + try { + ClientTickEvents.END_WORLD_TICK.register(clientWorld -> tickClient()); + } catch (NoClassDefFoundError ignored) { + try { + WorldTickCallback.EVENT.register(world -> { + if (world.isClient) { + tickClient(); + } + }); + } catch (NoClassDefFoundError ignored2) { + ViaFabric.JLOGGER.info("Fabric Lifecycle V0/V1 isn't installed"); + } + } + } + + public void registerServerTick() { + try { + ServerTickEvents.END_WORLD_TICK.register(this::tickServer); + } catch (NoClassDefFoundError ignored) { + WorldTickCallback.EVENT.register(world -> { + if (!world.isClient) { + tickServer(world); + } + }); + } + } + + private void tickClient() { + ClientPlayerEntity p = MinecraftClient.getInstance().player; + if (p != null) { + clientItem = fromNative(p.inventory.getMainHandStack()); + } + } + + private void tickServer(World world) { + serverPlayers.clear(); + world.getPlayers().forEach(it -> serverPlayers + .put(it.getUuid(), fromNative(it.inventory.getMainHandStack()))); + } + + private Item fromNative(ItemStack original) { + Identifier iid = Registry.ITEM.getId(original.getItem()); + if (iid == null) return new Item(0, (byte) 0, (short) 0, null); + int id = swordId(iid.toString()); + return new Item(id, (byte) original.getCount(), (short) original.getDamage(), null); + } + + private int swordId(String id) { + // https://github.com/ViaVersion/ViaVersion/blob/8de26a0ad33f5b739f5394ed80f69d14197fddc7/common/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java#L86 + switch (id) { + case "minecraft:iron_sword": + return 267; + case "minecraft:wooden_sword": + return 268; + case "minecraft:golden_sword": + return 272; + case "minecraft:diamond_sword": + return 276; + case "minecraft:stone_sword": + return 283; + } + return 0; + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRVersionProvider.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRVersionProvider.java new file mode 100644 index 0000000..58228de --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/providers/VRVersionProvider.java @@ -0,0 +1,166 @@ +package com.viaversion.fabric.mc116.providers; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.ViaFabricAddress; +import com.viaversion.fabric.mc116.service.ProtocolAutoDetector; +import com.viaversion.fabric.mc116.util.ProtocolUtils; +import com.google.common.primitives.Ints; +import com.viaversion.viaversion.api.connection.ProtocolInfo; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.network.ClientConnection; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.exception.CancelException; +import com.viaversion.viaversion.api.protocol.packet.State; +import com.viaversion.viaversion.protocols.base.*; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.*; +import java.util.stream.IntStream; + +public class VRVersionProvider extends BaseVersionProvider { + private int[] multiconnectSupportedVersions = null; + + { + try { + if (FabricLoader.getInstance().isModLoaded("multiconnect")) { + Class mcApiClass = Class.forName("net.earthcomputer.multiconnect.api.MultiConnectAPI"); + Class iProtocolClass = Class.forName("net.earthcomputer.multiconnect.api.IProtocol"); + Object mcApiInstance = mcApiClass.getMethod("instance").invoke(null); + List protocols = (List) mcApiClass.getMethod("getSupportedProtocols").invoke(mcApiInstance); + Method getValue = iProtocolClass.getMethod("getValue"); + Method isMulticonnectBeta; + try { + isMulticonnectBeta = iProtocolClass.getMethod("isMulticonnectBeta"); + } catch (NoSuchMethodException e) { + isMulticonnectBeta = null; + } + Set vers = new TreeSet<>(); + for (Object protocol : protocols) { + // Do not use versions with beta multiconnect support, which may have stability issues + if (isMulticonnectBeta == null || !(Boolean) isMulticonnectBeta.invoke(protocol)) { + vers.add((Integer) getValue.invoke(protocol)); + } + } + multiconnectSupportedVersions = vers.stream().mapToInt(Integer::intValue).toArray(); + ViaFabric.JLOGGER.info("ViaFabric will integrate with multiconnect"); + } + } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException + | ClassCastException ignored) { + } + } + + @Override + public int getClosestServerProtocol(UserConnection connection) throws Exception { + if (connection.isClientSide()) { + ProtocolInfo info = Objects.requireNonNull(connection.getProtocolInfo()); + + if (!ViaFabric.config.isClientSideEnabled()) { + return info.getProtocolVersion(); + } + + int serverVer = ViaFabric.config.getClientSideVersion(); + SocketAddress addr = connection.getChannel().remoteAddress(); + + if (addr instanceof InetSocketAddress) { + int addrVersion = new ViaFabricAddress().parse(((InetSocketAddress) addr).getHostName()).protocol; + if (addrVersion != 0) serverVer = addrVersion; + + try { + if (serverVer == -2) { + // Hope protocol was autodetected + ProtocolVersion autoVer = + ProtocolAutoDetector.detectVersion((InetSocketAddress) addr).getNow(null); + if (autoVer != null) { + serverVer = autoVer.getVersion(); + } + } + } catch (Exception e) { + ViaFabric.JLOGGER.warning("Couldn't auto detect: " + e); + } + } + + boolean blocked = checkAddressBlocked(addr); + boolean supported = ProtocolUtils.isSupported(serverVer, info.getProtocolVersion()); + + handleMulticonnectPing(connection, info, blocked, serverVer); + + if (blocked || !supported) serverVer = info.getProtocolVersion(); + + return serverVer; + } + return super.getClosestServerProtocol(connection); + } + + private boolean checkAddressBlocked(SocketAddress addr) { + return addr instanceof InetSocketAddress && (isDisabled(((InetSocketAddress) addr).getHostString()) + || ((((InetSocketAddress) addr).getAddress() != null) && + (isDisabled(((InetSocketAddress) addr).getAddress().getHostAddress()) + || isDisabled(((InetSocketAddress) addr).getAddress().getHostName())))); + } + + private void handleMulticonnectPing(UserConnection connection, ProtocolInfo info, boolean blocked, int serverVer) throws Exception { + if (info.getState() == State.STATUS + && info.getProtocolVersion() == -1 + && connection.getChannel().pipeline().get(ClientConnection.class).getPacketListener() + .getClass().getName().startsWith("net.earthcomputer.multiconnect") + && (blocked || ProtocolUtils.isSupported(serverVer, getVersionForMulticonnect(serverVer)))) { // Intercept the connection + int multiconnectSuggestion = blocked ? -1 : getVersionForMulticonnect(serverVer); + ViaFabric.JLOGGER.info("Sending " + ProtocolVersion.getProtocol(multiconnectSuggestion) + " for multiconnect version detector"); + PacketWrapper newAnswer = PacketWrapper.create(0x00, null, connection); + newAnswer.write(Type.STRING, "{\"version\":{\"name\":\"viafabric integration\",\"protocol\":" + multiconnectSuggestion + "}}"); + newAnswer.send(info.getPipeline().contains(BaseProtocol1_16.class) ? BaseProtocol1_16.class : BaseProtocol1_7.class, true, true); + throw CancelException.generate(); + } + } + + private int getVersionForMulticonnect(int clientSideVersion) { + // https://github.com/ViaVersion/ViaVersion/blob/master/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityVersionProvider.java + int[] compatibleProtocols = multiconnectSupportedVersions; + + if (Arrays.binarySearch(compatibleProtocols, clientSideVersion) >= 0) { + return clientSideVersion; + } + + if (clientSideVersion < compatibleProtocols[0]) { + return compatibleProtocols[0]; + } + + // TODO: This needs a better fix, i.e checking ProtocolRegistry to see if it would work. + for (int i = compatibleProtocols.length - 1; i >= 0; i--) { + int protocol = compatibleProtocols[i]; + if (clientSideVersion > protocol && ProtocolVersion.isRegistered(protocol)) { + return protocol; + } + } + + ViaFabric.JLOGGER.severe("multiconnect integration: Panic, no protocol id found for " + clientSideVersion); + return clientSideVersion; + } + + private boolean isDisabled(String addr) { + String[] parts = addr.split("\\."); + boolean isNumericIp = parts.length == 4 && Arrays.stream(parts).map(Ints::tryParse).allMatch(Objects::nonNull); + return IntStream.range(0, parts.length).anyMatch(i -> { + String query; + if (isNumericIp) { + query = String.join(".", Arrays.stream(parts, 0, i + 1) + .toArray(String[]::new)) + ((i != 3) ? ".*" : ""); + } else { + query = ((i != 0) ? "*." : "") + String.join(".", Arrays.stream(parts, i, parts.length) + .toArray(String[]::new)); + } + if (ViaFabric.config.isForcedDisable(query)) { + ViaFabric.JLOGGER.info(addr + " is force-disabled. (Matches " + query + ")"); + return true; + } else { + return false; + } + }); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/service/ProtocolAutoDetector.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/service/ProtocolAutoDetector.java new file mode 100644 index 0000000..2013a4b --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/service/ProtocolAutoDetector.java @@ -0,0 +1,125 @@ +package com.viaversion.fabric.mc116.service; + +import com.viaversion.fabric.mc116.ViaFabric; +import com.viaversion.fabric.mc116.ViaFabricAddress; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +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.network.*; +import net.minecraft.network.listener.ClientQueryPacketListener; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.network.packet.c2s.query.QueryRequestC2SPacket; +import net.minecraft.network.packet.s2c.query.QueryPongS2CPacket; +import net.minecraft.network.packet.s2c.query.QueryResponseS2CPacket; +import net.minecraft.server.ServerMetadata; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +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 LoadingCache> SERVER_VER = CacheBuilder.newBuilder() + .expireAfterAccess(100, 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() { + protected void initChannel(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()) + .addLast("decoder", new DecoderHandler(NetworkSide.CLIENTBOUND)) + .addLast("prepender", new SizePrepender()) + .addLast("encoder", new PacketEncoder(NetworkSide.SERVERBOUND)) + .addLast("packet_handler", clientConnection); + } + }) + .connect(address); + + ch.addListener(future1 -> { + if (!future1.isSuccess()) { + future.completeExceptionally(future1.cause()); + } else { + ch.channel().eventLoop().execute(() -> { // needs to execute after channel init + clientConnection.setPacketListener(new ClientQueryPacketListener() { + @Override + public void onResponse(QueryResponseS2CPacket packet) { + ServerMetadata meta = packet.getServerMetadata(); + ServerMetadata.Version version; + if (meta != null && (version = meta.getVersion()) != null) { + ProtocolVersion ver = ProtocolVersion.getProtocol(version.getProtocolVersion()); + future.complete(ver); + ViaFabric.JLOGGER.info("Auto-detected " + ver + " for " + address); + } else { + future.completeExceptionally(new IllegalArgumentException("Null version in query response")); + } + clientConnection.disconnect(LiteralText.EMPTY); + } + + @Override + public void onPong(QueryPongS2CPacket packet) { + clientConnection.disconnect(new LiteralText("Pong not requested!")); + } + + @Override + public void onDisconnected(Text reason) { + future.completeExceptionally(new IllegalStateException(reason.asString())); + } + + @Override + public ClientConnection getConnection() { + return clientConnection; + } + }); + + clientConnection.send(new HandshakeC2SPacket(address.getHostString(), + address.getPort(), NetworkState.STATUS)); + clientConnection.send(new QueryRequestC2SPacket()); + }); + } + }); + } catch (Throwable throwable) { + future.completeExceptionally(throwable); + } + + return future; + })); + + public static CompletableFuture detectVersion(InetSocketAddress address) { + try { + InetSocketAddress real = new InetSocketAddress(InetAddress.getByAddress + (new ViaFabricAddress().parse(address.getHostString()).realAddress, + 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-mc116/src/main/java/com/viaversion/fabric/mc116/util/FutureTaskId.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/FutureTaskId.java new file mode 100644 index 0000000..71c0846 --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/FutureTaskId.java @@ -0,0 +1,23 @@ +package com.viaversion.fabric.mc116.util; + +import com.viaversion.viaversion.api.platform.PlatformTask; + +import java.util.concurrent.Future; + +public class FutureTaskId implements PlatformTask> { + private final Future object; + + public FutureTaskId(Future object) { + this.object = object; + } + + @Override + public Future getObject() { + return object; + } + + @Override + public void cancel() { + object.cancel(false); + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/JLoggerToLog4j.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/JLoggerToLog4j.java new file mode 100644 index 0000000..3bb5a5f --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/JLoggerToLog4j.java @@ -0,0 +1,68 @@ +package com.viaversion.fabric.mc116.util; + +import java.text.MessageFormat; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public class JLoggerToLog4j extends Logger { + private final org.apache.logging.log4j.Logger base; + + public JLoggerToLog4j(org.apache.logging.log4j.Logger logger) { + super("logger", null); + this.base = logger; + } + + public void log(LogRecord record) { + this.log(record.getLevel(), record.getMessage()); + } + + public void log(Level level, String msg) { + if (level == Level.FINE) { + this.base.debug(msg); + } else if (level == Level.WARNING) { + this.base.warn(msg); + } else if (level == Level.SEVERE) { + this.base.error(msg); + } else if (level == Level.INFO) { + this.base.info(msg); + } else { + this.base.trace(msg); + } + + } + + public void log(Level level, String msg, Object param1) { + if (level == Level.FINE) { + this.base.debug(msg, param1); + } else if (level == Level.WARNING) { + this.base.warn(msg, param1); + } else if (level == Level.SEVERE) { + this.base.error(msg, param1); + } else if (level == Level.INFO) { + this.base.info(msg, param1); + } else { + this.base.trace(msg, param1); + } + + } + + public void log(Level level, String msg, Object[] params) { + log(level, MessageFormat.format(msg, params)); + } + + public void log(Level level, String msg, Throwable params) { + if (level == Level.FINE) { + this.base.debug(msg, params); + } else if (level == Level.WARNING) { + this.base.warn(msg, params); + } else if (level == Level.SEVERE) { + this.base.error(msg, params); + } else if (level == Level.INFO) { + this.base.info(msg, params); + } else { + this.base.trace(msg, params); + } + + } +} diff --git a/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/ProtocolUtils.java b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/ProtocolUtils.java new file mode 100644 index 0000000..a7d8bca --- /dev/null +++ b/viafabric-mc116/src/main/java/com/viaversion/fabric/mc116/util/ProtocolUtils.java @@ -0,0 +1,60 @@ +package com.viaversion.fabric.mc116.util; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; + +import java.util.Arrays; +import java.util.stream.Stream; + +public class ProtocolUtils { + public static boolean isSupported(int server, int client) { + return server == client || Via.getManager().getProtocolManager().getProtocolPath(client, server) != null; + } + + public static String getProtocolName(int id) { + if (!ProtocolVersion.isRegistered(id)) return Integer.toString(id); + return ProtocolVersion.getProtocol(id).getName(); + } + + public static boolean isStartOfProtocolText(String s) { + try { + Integer.parseInt(s); + return true; + } catch (NumberFormatException e) { + try { + Integer.parseInt(s + '0'); + return true; + } catch (NumberFormatException e2) { + return ProtocolVersion.getProtocols().stream() + .map(ProtocolVersion::getName) + .flatMap(str -> Stream.concat( + Arrays.stream(str.split("-")), + Arrays.stream(new String[]{str}) + )) + .anyMatch(ver -> ver.startsWith(s)); + } + } + } + + public static Integer parseProtocolId(String s) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException ignored) { + ProtocolVersion closest = ProtocolVersion.getClosest(s); + if (closest == null) return null; + return closest.getVersion(); + } + } + + public static String[] getProtocolSuggestions(String text) { + return ProtocolVersion.getProtocols().stream() + .map(ProtocolVersion::getName) + .flatMap(str -> Stream.concat( + Arrays.stream(str.split("-")), + Arrays.stream(new String[]{str}) + )) + .distinct() + .filter(ver -> ver.startsWith(text)) + .toArray(String[]::new); + } +} diff --git a/viafabric-mc116/src/main/resources/fabric.mod.json b/viafabric-mc116/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..01f96b8 --- /dev/null +++ b/viafabric-mc116/src/main/resources/fabric.mod.json @@ -0,0 +1,57 @@ +{ + "schemaVersion": 1, + "id": "viafabric-mc116", + "name": "ViaFabric", + "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/" + } + } + ], + "contributors": [ + { + "name": "GitHub contributors", + "contact": { + "homepage": "https://github.com/ViaVersion/ViaFabric/graphs/contributors" + } + } + ], + "entrypoints": { + "main": [ + "com.viaversion.fabric.mc116.ViaFabric" + ], + "modmenu": [ + "com.viaversion.fabric.mc116.gui.ModMenuConfig" + ] + }, + "depends": { + "fabricloader": ">=0.4.0", + "fabric-resource-loader-v0": "*", + "minecraft": "1.16.x", + "viaversion": ">3.0.1" + }, + "conflicts": { + "fabric-registry-sync-v0": "*" + }, + "recommends": { + "fabric-command-api-v1": "*" + }, + "icon": "assets/viafabric/textures/logo.png", + "mixins": [ + "mixins.viafabric.address.json", + "mixins.viafabric.gui.json", + "mixins.viafabric.debug.json", + "mixins.viafabric.pipeline.json" + ] +} diff --git a/viafabric-mc116/src/main/resources/mixins.viafabric.address.json b/viafabric-mc116/src/main/resources/mixins.viafabric.address.json new file mode 100644 index 0000000..b7fde22 --- /dev/null +++ b/viafabric-mc116/src/main/resources/mixins.viafabric.address.json @@ -0,0 +1,14 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc116.mixin.address.client.MixinConnectScreenThread", + "com.viaversion.fabric.mc116.mixin.address.client.MixinServerAddress", + "com.viaversion.fabric.mc116.mixin.address.client.MixinServerPinger" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/viafabric-mc116/src/main/resources/mixins.viafabric.debug.json b/viafabric-mc116/src/main/resources/mixins.viafabric.debug.json new file mode 100644 index 0000000..73efb02 --- /dev/null +++ b/viafabric-mc116/src/main/resources/mixins.viafabric.debug.json @@ -0,0 +1,13 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc116.mixin.debug.client.MixinClientConnectionAccessor", + "com.viaversion.fabric.mc116.mixin.debug.client.MixinDebugHud" + ], + "injectors": { + "defaultRequire": 0 + } +} diff --git a/viafabric-mc116/src/main/resources/mixins.viafabric.gui.json b/viafabric-mc116/src/main/resources/mixins.viafabric.gui.json new file mode 100644 index 0000000..975f57e --- /dev/null +++ b/viafabric-mc116/src/main/resources/mixins.viafabric.gui.json @@ -0,0 +1,12 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "com.viaversion.fabric.mc116.mixin.gui.client.MixinMultiplayerScreen" + ], + "injectors": { + "defaultRequire": 0 + } +} diff --git a/viafabric-mc116/src/main/resources/mixins.viafabric.pipeline.json b/viafabric-mc116/src/main/resources/mixins.viafabric.pipeline.json new file mode 100644 index 0000000..e142c44 --- /dev/null +++ b/viafabric-mc116/src/main/resources/mixins.viafabric.pipeline.json @@ -0,0 +1,14 @@ +{ + "required": true, + "compatibilityLevel": "JAVA_8", + "mixins": [ + "com.viaversion.fabric.mc116.mixin.pipeline.MixinClientConnection", + "com.viaversion.fabric.mc116.mixin.pipeline.MixinServerNetworkIoChInit" + ], + "client": [ + "com.viaversion.fabric.mc116.mixin.pipeline.client.MixinClientConnectionChInit" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/viafabric-mc117/src/main/resources/fabric.mod.json b/viafabric-mc117/src/main/resources/fabric.mod.json index 21e83f8..cb9fce2 100644 --- a/viafabric-mc117/src/main/resources/fabric.mod.json +++ b/viafabric-mc117/src/main/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "viafabric-impl", + "id": "viafabric-mc117", "name": "ViaFabric", "version": "@version@", "description": "@description@", @@ -8,8 +8,7 @@ "contact": { "homepage": "https://viaversion.com/fabric", "issues": "https://github.com/ViaVersion/ViaFabric/issues", - "sources": "https://github.com/ViaVersion/ViaFabric", - "discord": "https://discord.gg/viaversion" + "sources": "https://github.com/ViaVersion/ViaFabric" }, "environment": "*", "authors": [ diff --git a/viafabric-mc117/src/main/resources/mixins.viafabric.address.json b/viafabric-mc117/src/main/resources/mixins.viafabric.address.json index 93343d4..47399a5 100644 --- a/viafabric-mc117/src/main/resources/mixins.viafabric.address.json +++ b/viafabric-mc117/src/main/resources/mixins.viafabric.address.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc117.mixin.address", "mixins": [ ], "client": [ - "client.MixinConnectScreenThread", - "client.MixinServerAddress", - "client.MixinServerPinger" + "com.viaversion.fabric.mc117.mixin.address.client.MixinConnectScreenThread", + "com.viaversion.fabric.mc117.mixin.address.client.MixinServerAddress", + "com.viaversion.fabric.mc117.mixin.address.client.MixinServerPinger" ], "injectors": { "defaultRequire": 1 diff --git a/viafabric-mc117/src/main/resources/mixins.viafabric.debug.json b/viafabric-mc117/src/main/resources/mixins.viafabric.debug.json index 8660d77..a3b7aa8 100644 --- a/viafabric-mc117/src/main/resources/mixins.viafabric.debug.json +++ b/viafabric-mc117/src/main/resources/mixins.viafabric.debug.json @@ -1,12 +1,11 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc117.mixin.debug", "mixins": [ ], "client": [ - "client.MixinClientConnectionAccessor", - "client.MixinDebugHud" + "com.viaversion.fabric.mc117.mixin.debug.client.MixinClientConnectionAccessor", + "com.viaversion.fabric.mc117.mixin.debug.client.MixinDebugHud" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc117/src/main/resources/mixins.viafabric.gui.json b/viafabric-mc117/src/main/resources/mixins.viafabric.gui.json index 653c16e..cdf1f1b 100644 --- a/viafabric-mc117/src/main/resources/mixins.viafabric.gui.json +++ b/viafabric-mc117/src/main/resources/mixins.viafabric.gui.json @@ -1,11 +1,10 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc117.mixin.gui", "mixins": [ ], "client": [ - "client.MixinMultiplayerScreen" + "com.viaversion.fabric.mc117.mixin.gui.client.MixinMultiplayerScreen" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc117/src/main/resources/mixins.viafabric.pipeline.json b/viafabric-mc117/src/main/resources/mixins.viafabric.pipeline.json index 3f7b570..396a1af 100644 --- a/viafabric-mc117/src/main/resources/mixins.viafabric.pipeline.json +++ b/viafabric-mc117/src/main/resources/mixins.viafabric.pipeline.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc117.mixin.pipeline", "mixins": [ - "MixinClientConnection", - "MixinServerNetworkIoChInit" + "com.viaversion.fabric.mc117.mixin.pipeline.MixinClientConnection", + "com.viaversion.fabric.mc117.mixin.pipeline.MixinServerNetworkIoChInit" ], "client": [ - "client.MixinClientConnectionChInit" + "com.viaversion.fabric.mc117.mixin.pipeline.client.MixinClientConnectionChInit" ], "injectors": { "defaultRequire": 1 diff --git a/viafabric-mc18/build.gradle.kts b/viafabric-mc18/build.gradle.kts index 5427898..e750736 100644 --- a/viafabric-mc18/build.gradle.kts +++ b/viafabric-mc18/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.function.Function as JFun version = rootProject.version dependencies { @@ -7,4 +8,10 @@ dependencies { modImplementation("net.legacyfabric.legacy-fabric-api:legacy-fabric-api:1.1.0+1.8.9") modImplementation("io.github.boogiemonster1o1:modmenu:0.1.0+1.8.9") +} + +minecraft { + intermediaryUrl = JFun { + "https://maven.legacyfabric.net/net/fabricmc/intermediary/$it/intermediary-$it-v2.jar" + } } \ No newline at end of file diff --git a/viafabric-mc18/src/main/resources/fabric.mod.json b/viafabric-mc18/src/main/resources/fabric.mod.json index 376bf40..b7f55f0 100644 --- a/viafabric-mc18/src/main/resources/fabric.mod.json +++ b/viafabric-mc18/src/main/resources/fabric.mod.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "id": "viafabric-impl", + "id": "viafabric-mc18", "name": "ViaFabric", "version": "@version@", "description": "@description@", diff --git a/viafabric-mc18/src/main/resources/mixins.viafabric.address.json b/viafabric-mc18/src/main/resources/mixins.viafabric.address.json index 0228c90..f4c2a97 100644 --- a/viafabric-mc18/src/main/resources/mixins.viafabric.address.json +++ b/viafabric-mc18/src/main/resources/mixins.viafabric.address.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc18.mixin.address", "mixins": [ ], "client": [ - "client.MixinConnectScreenThread", - "client.MixinServerAddress", - "client.MixinServerPinger" + "com.viaversion.fabric.mc18.mixin.address.client.MixinConnectScreenThread", + "com.viaversion.fabric.mc18.mixin.address.client.MixinServerAddress", + "com.viaversion.fabric.mc18.mixin.address.client.MixinServerPinger" ], "injectors": { "defaultRequire": 1 diff --git a/viafabric-mc18/src/main/resources/mixins.viafabric.debug.json b/viafabric-mc18/src/main/resources/mixins.viafabric.debug.json index 0d7a00a..9ba2e1c 100644 --- a/viafabric-mc18/src/main/resources/mixins.viafabric.debug.json +++ b/viafabric-mc18/src/main/resources/mixins.viafabric.debug.json @@ -1,12 +1,11 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc18.mixin.debug", "mixins": [ ], "client": [ - "client.MixinClientConnectionAccessor", - "client.MixinDebugHud" + "com.viaversion.fabric.mc18.mixin.debug.client.MixinClientConnectionAccessor", + "com.viaversion.fabric.mc18.mixin.debug.client.MixinDebugHud" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc18/src/main/resources/mixins.viafabric.gui.json b/viafabric-mc18/src/main/resources/mixins.viafabric.gui.json index 2723b1f..485dfcc 100644 --- a/viafabric-mc18/src/main/resources/mixins.viafabric.gui.json +++ b/viafabric-mc18/src/main/resources/mixins.viafabric.gui.json @@ -1,11 +1,10 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc18.mixin.gui", "mixins": [ ], "client": [ - "client.MixinMultiplayerScreen" + "com.viaversion.fabric.mc18.mixin.gui.client.MixinMultiplayerScreen" ], "injectors": { "defaultRequire": 0 diff --git a/viafabric-mc18/src/main/resources/mixins.viafabric.pipeline.json b/viafabric-mc18/src/main/resources/mixins.viafabric.pipeline.json index 98d56c5..c1d994d 100644 --- a/viafabric-mc18/src/main/resources/mixins.viafabric.pipeline.json +++ b/viafabric-mc18/src/main/resources/mixins.viafabric.pipeline.json @@ -1,13 +1,12 @@ { "required": true, "compatibilityLevel": "JAVA_8", - "package": "com.viaversion.fabric.mc18.mixin.pipeline", "mixins": [ - "MixinClientConnection", - "MixinServerNetworkIoChInit" + "com.viaversion.fabric.mc18.mixin.pipeline.MixinClientConnection", + "com.viaversion.fabric.mc18.mixin.pipeline.MixinServerNetworkIoChInit" ], "client": [ - "client.MixinClientConnectionChInit" + "com.viaversion.fabric.mc18.mixin.pipeline.client.MixinClientConnectionChInit" ], "injectors": { "defaultRequire": 1