From f5591e7df3dc962ae1f6c9512daa88e609d24f29 Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Fri, 25 Jul 2025 02:03:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=E8=A7=A3=E5=86=B3=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E7=BB=84=E7=8E=AF=E5=A2=83=E4=B8=8B=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=AF=BC=E8=87=B4=E7=9A=84=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E6=95=B0=E6=8D=AE=E5=8C=85=E8=A7=A3=E6=9E=90=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E9=97=AE=E9=A2=98=E6=97=B6=EF=BC=88=E5=A6=82ClientAdd?= =?UTF-8?q?EntityPacket=E6=95=B0=E6=8D=AE=E8=AF=BB=E5=8F=96=E9=94=99?= =?UTF-8?q?=E8=AF=AF=EF=BC=89=EF=BC=8C=E6=88=91=E6=9C=80=E5=88=9D=E5=8F=91?= =?UTF-8?q?=E7=8E=B0=E9=97=AE=E9=A2=98=E6=BA=90=E4=BA=8ENetty=E7=AE=A1?= =?UTF-8?q?=E9=81=93=E4=BC=A0=E9=80=92=E6=9C=BA=E5=88=B6=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=E7=96=8F=E6=BC=8F=E3=80=82=E7=84=B6=E8=80=8C=E8=BF=9B?= =?UTF-8?q?=E4=B8=80=E6=AD=A5=E6=8E=92=E6=9F=A5=E5=8F=91=E7=8E=B0=EF=BC=8C?= =?UTF-8?q?=E5=8F=A6=E4=B8=80=E4=B8=AA=E5=85=B3=E8=81=94=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E5=B7=A5=E4=BD=9C=E6=9C=BA=E5=88=B6=E6=89=8D=E6=98=AF?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=9B=A0=E7=B4=A0=E3=80=82=E7=BB=8F=E6=B7=B1?= =?UTF-8?q?=E5=85=A5=E5=88=86=E6=9E=90=E5=85=B6=E4=BA=A4=E4=BA=92=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=90=8E=EF=BC=8C=E6=88=91=E5=86=B3=E5=AE=9A=E5=B0=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=B8=8D=E5=AE=8C=E5=96=84=E7=9A=84ForgeClie?= =?UTF-8?q?ntResetPacket=E6=A8=A1=E7=BB=84=E6=95=B4=E5=90=88=E5=88=B0?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E9=A1=B9=E7=9B=AE=E4=B8=AD=E3=80=82=E7=BB=8F?= =?UTF-8?q?=E8=BF=87=E5=A4=9A=E6=AC=A1=E7=89=88=E6=9C=AC=E8=BF=AD=E4=BB=A3?= =?UTF-8?q?=E5=92=8C=E6=8C=81=E7=BB=AD=E8=B0=83=E8=AF=95=EF=BC=8C=E6=9C=80?= =?UTF-8?q?=E7=BB=88=E6=88=90=E5=8A=9F=E5=AE=9E=E7=8E=B0=E4=BA=86=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E7=9A=84=E8=B7=A8=E6=9C=8D=E5=AE=9E=E4=BD=93=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8A=9F=E8=83=BD=E3=80=82=EF=BC=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 ++ forge-mod/build.gradle | 18 +++-- forge-mod/gradle.properties | 2 +- .../crossmod/CrossTeleportMod.java | 10 ++- .../crossmod/NetworkHandler.java | 6 +- .../crossmod/client/CrossServerGui.java | 2 +- .../crossmod/client/PluginChannelClient.java | 18 +++-- .../crossmod/mixin/AccessorMinecraft.java | 12 +++ .../mixin/MixinMUINetWorkHandler.java | 26 +++++++ .../crossmod/mixin/ModListSpoofMixin.java | 23 ++++++ .../crossmod/reset/ClientResetManager.java | 75 +++++++++++++++++++ .../crossmod/reset/ResetHelper.java | 47 ++++++++++++ .../crossmod/reset/ResetPacket.java | 64 ++++++++++++++++ .../crossmod/util/DebugUtils.java | 57 ++++++++++++++ .../resources/META-INF/accesstransformer.cfg | 1 + .../assets/ltdcrossteleport/lang/en_us.json | 7 +- .../assets/ltdcrossteleport/lang/zh_cn.json | 8 +- .../resources/ltdcrossteleport.mixins.json | 11 +++ velocity-plugin/gradle.properties | 2 +- .../command/ReloadConfigCommand.java | 2 +- .../listener/PluginMessageListener.java | 26 ++++--- 21 files changed, 388 insertions(+), 36 deletions(-) create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/AccessorMinecraft.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/ModListSpoofMixin.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java create mode 100644 forge-mod/src/main/resources/ltdcrossteleport.mixins.json diff --git a/build.gradle b/build.gradle index 6e980a1..aec784b 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,13 @@ allprojects { maven { url = "https://maven.izzel.io/releases/" } maven { url = "https://maven.bawnorton.com/releases" } maven { url 'https://repo.lucko.me/' } // LuckPerms + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + maven { + url "https://cursemaven.com" + } } processResources{ duplicatesStrategy = DuplicatesStrategy.EXCLUDE diff --git a/forge-mod/build.gradle b/forge-mod/build.gradle index 792a7e6..679f4a8 100644 --- a/forge-mod/build.gradle +++ b/forge-mod/build.gradle @@ -32,11 +32,11 @@ println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty ' -//// 配置 Mixin -//mixin { -// add sourceSets.main, "${mod_id}.refmap.json" -// config "${mod_id}.mixins.json" -//} +// 配置 Mixin +mixin { + add sourceSets.main, "${mod_id}.refmap.json" + config "${mod_id}.mixins.json" +} // 配置 LegacyForge 运行环境 legacyForge { @@ -93,6 +93,12 @@ configurations { dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' + modImplementation "curse.maven:easy-villagers-400514:3887794" + modImplementation "curse.maven:xaeros-world-map-317780:6538320" + modImplementation "curse.maven:immersive-aircraft-666014:4679496" + modImplementation "curse.maven:modern-ui-352491:5229350" + modImplementation "curse.maven:iceberg-520110:4035917" } // 编译任务优化 @@ -114,7 +120,7 @@ jar { 'Implementation-Version' : archiveVersion, 'Implementation-Vendor' : mod_authors, 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), -// 'MixinConfigs' : "${mod_id}.mixin.json" + 'MixinConfigs' : "${mod_id}.mixins.json" ]) } finalizedBy 'reobfJar' diff --git a/forge-mod/gradle.properties b/forge-mod/gradle.properties index 8ed5f68..d51ac2d 100644 --- a/forge-mod/gradle.properties +++ b/forge-mod/gradle.properties @@ -49,7 +49,7 @@ mod_name=Leisure Time Dock Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=0.0.0.2 +mod_version=0.0.1.3 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java index 8b738ba..898dbd8 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java @@ -1,11 +1,17 @@ package com.leisuretimedock.crossmod; +import com.leisuretimedock.crossmod.reset.ClientResetManager; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.network.NetworkConstants; + +import java.util.concurrent.atomic.AtomicBoolean; @Mod(CrossTeleportMod.MOD_ID) public class CrossTeleportMod { @@ -15,7 +21,9 @@ public class CrossTeleportMod { public CrossTeleportMod() { // 注册生命周期事件 ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, - () -> new IExtensionPoint.DisplayTest(() -> "ANY", (a, b) -> true)); + () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + modEventBus.addListener(ClientResetManager::init); } @Mod.EventBusSubscriber(modid = MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE) diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java index 8551797..6a47a8b 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java @@ -16,7 +16,6 @@ import java.util.Objects; public class NetworkHandler { // 自定义插件消息通道标识 - public static final ResourceLocation TELEPORT_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport"); public static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "channel"); public static void register() { @@ -30,9 +29,8 @@ public class NetworkHandler { * @param payload 负载数据(字节数组) */ public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -// buf.writeUtf(subChannel.getPath()); // 写入子通道字符串 - buf.writeBytes(payload); // 写入负载字节 + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length)); + buf.writeBytes(payload); // 获取当前连接并发送自定义负载包 Objects.requireNonNull(Minecraft.getInstance().getConnection()) diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/CrossServerGui.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/CrossServerGui.java index d16c073..5b4662d 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/CrossServerGui.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/CrossServerGui.java @@ -58,7 +58,7 @@ public class CrossServerGui extends Screen { private void sendCustomPayload(String message) { Minecraft mc = Minecraft.getInstance(); if (mc.getConnection() != null) { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256)); buf.writeUtf(message); mc.getConnection().send(new ServerboundCustomPayloadPacket(CHANNEL_ID, buf)); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java index 3269370..6440485 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java @@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.client; import com.leisuretimedock.crossmod.CrossTeleportMod; import com.leisuretimedock.crossmod.NetworkHandler; +import com.leisuretimedock.crossmod.reset.ClientResetManager; import com.mojang.brigadier.arguments.StringArgumentType; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; @@ -28,7 +29,8 @@ public class PluginChannelClient { @SubscribeEvent public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) { log.debug("[CrossTeleportMod] 玩家登录事件触发"); - + if (ClientResetManager.isNegotiating.get()) + ClientResetManager.isNegotiating.set(false); Connection connection = Objects.requireNonNull(Minecraft.getInstance().getConnection()).getConnection(); ChannelPipeline pipeline = connection.channel().pipeline(); @@ -38,13 +40,11 @@ public class PluginChannelClient { pipeline.addBefore("packet_handler", HANDLER_NAME, new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, ClientboundCustomPayloadPacket packet) { - log.debug("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier()); - if (!packet.getIdentifier().equals(NetworkHandler.CHANNEL_ID)) { - log.warn("[CrossTeleportMod] 未识别插件消息频道: {}", packet.getIdentifier()); + ctx.fireChannelRead(packet); return; } - + log.debug("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier()); FriendlyByteBuf buf = packet.getData(); try { // 先读一个字符串但不使用它,出现空消息 @@ -96,9 +96,11 @@ public class PluginChannelClient { log.debug("[CrossTeleportMod] 当前管线内容: {}", pipeline.names()); - if (pipeline.get(HANDLER_NAME) != null) { - pipeline.remove(HANDLER_NAME); - log.debug("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME); + if (pipeline.get(HANDLER_NAME) != null ) { + if (!ClientResetManager.isNegotiating.get()) { + pipeline.remove(HANDLER_NAME); + log.debug("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME); + } else log.debug("[CrossTeleport] 跳转中,不移除消息处理器: {}", HANDLER_NAME); } else { log.warn("[CrossTeleportMod] 未找到插件消息处理器: {}", HANDLER_NAME); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/AccessorMinecraft.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/AccessorMinecraft.java new file mode 100644 index 0000000..f19d35b --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/AccessorMinecraft.java @@ -0,0 +1,12 @@ +package com.leisuretimedock.crossmod.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Minecraft.class) +public interface AccessorMinecraft { + @Accessor("pendingConnection") + void setPendingConnection(Connection connection); +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java new file mode 100644 index 0000000..d33733d --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java @@ -0,0 +1,26 @@ +package com.leisuretimedock.crossmod.mixin; + +import icyllis.modernui.mc.forge.NetworkHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Pseudo +@Mixin(value = NetworkHandler.class, remap = false) +public class MixinMUINetWorkHandler { + /** + * 修补构造 ResourceLocation("modernui", id) 时,若 id 是空字符串,则替换为 "default" + */ + @ModifyArg( + method = "", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/resources/ResourceLocation;(Ljava/lang/String;Ljava/lang/String;)V" + ), + index = 1 // 修改 id 参数 + ) + private String fixEmptyId(String id) { + return id == null || id.isEmpty() ? "default" : id; + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/ModListSpoofMixin.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/ModListSpoofMixin.java new file mode 100644 index 0000000..236b46e --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/ModListSpoofMixin.java @@ -0,0 +1,23 @@ +package com.leisuretimedock.crossmod.mixin; + +import net.minecraftforge.network.HandshakeMessages; +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 java.util.List; + +@Mixin(value = HandshakeMessages.C2SModListReply.class, remap = false) +public class ModListSpoofMixin { + @Inject(method = "*", at = @At("RETURN")) + private void injectFakeModList(CallbackInfo ci) { + HandshakeMessages.C2SModListReply self = HandshakeMessages.C2SModListReply.class.cast(this); + List mods = self.getModList(); + if (!mods.contains("clientresetpacket")) { + // "[Mixin] 模拟添加 clientresetpacket 模组到 modlist" ,以启用跳转功能 + mods.add("clientresetpacket"); + + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java new file mode 100644 index 0000000..3de6f5d --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java @@ -0,0 +1,75 @@ +package com.leisuretimedock.crossmod.reset; + +import lombok.extern.slf4j.Slf4j; +import net.minecraft.network.Connection; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import net.minecraftforge.network.*; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +public class ClientResetManager { + public static final Field handshakeField; + public static final Constructor contextConstructor; + public static AtomicBoolean isNegotiating = new AtomicBoolean(false); + public static SimpleChannel handshakeChannel; + + public static void init(FMLCommonSetupEvent event) { + + event.enqueueWork(() -> { + if (handshakeField == null) { + log.error( "Failed to find FML's handshake channel. Disabling mod."); + return; + } + if (contextConstructor == null) { + log.error("Failed to find FML's network event context constructor. Disabling mod."); + return; + } + try { + Object handshake = handshakeField.get(null); + if (handshake instanceof SimpleChannel) { + handshakeChannel = (SimpleChannel)handshake; + log.info("Registering forge reset packet."); + handshakeChannel.messageBuilder(ResetPacket.class, 98) + .loginIndex(ResetPacket::getLoginIndex, ResetPacket::setLoginIndex) + .decoder(ResetPacket::decode) + .encoder(ResetPacket::encode) + .consumer(HandshakeHandler.biConsumerFor(ResetPacket::handler)) + .add(); + log.info( "Registered forge reset packet successfully."); + } + } + catch (Exception e) { + log.error("Caught exception when attempting to utilize FML's handshake. Disabling mod. Exception: {}", e.getMessage()); + } + }); + } + private static Field fetchHandshakeChannel() { + try { + return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel"); + } + catch (Exception e) { + log.error("Exception occurred while accessing handshakeChannel: {}", e.getMessage(), e); + return null; + } + } + + private static Constructor fetchNetworkEventContext() { + try { + return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class); + } + catch (Exception e) { + log.error("Exception occurred while accessing getLoginIndex: {}", e.getMessage(), e); + return null; + } + } + + static { + handshakeField = fetchHandshakeChannel(); + contextConstructor = fetchNetworkEventContext(); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java new file mode 100644 index 0000000..ac22437 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java @@ -0,0 +1,47 @@ +package com.leisuretimedock.crossmod.reset; + +import lombok.extern.slf4j.Slf4j; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.ConnectScreen; +import net.minecraft.client.gui.screens.GenericDirtMessageScreen; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.multiplayer.resolver.ServerAddress; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.registries.GameData; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import static net.minecraft.ChatFormatting.BOLD; + +@Slf4j +@OnlyIn(Dist.CLIENT) +public class ResetHelper { + public static boolean clearClient(NetworkEvent.Context context) { + CompletableFuture future = context.enqueueWork(() -> { + log.debug("Clearing"); + Minecraft minecraft = Minecraft.getInstance(); + ServerData serverData = minecraft.getCurrentServer(); + if (minecraft.level == null) { + GameData.revertToFrozen(); + } + + minecraft.clearLevel(new GenericDirtMessageScreen(new TranslatableComponent("ltd.mod.client.negotiating").withStyle(BOLD))); + minecraft.setCurrentServer(serverData); + }); + log.debug("Waiting for Clear to complete"); + try { + future.get(); + log.debug("Clear complete, continuing reset"); + return true; + } catch (Exception e) { + log.error("Failed to clear client connection", e); + Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(new TranslatableComponent("ltd.mod.client.failed.reset_connection")); + return false; + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java new file mode 100644 index 0000000..dd785ca --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java @@ -0,0 +1,64 @@ +package com.leisuretimedock.crossmod.reset; + +import com.leisuretimedock.crossmod.mixin.AccessorMinecraft; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraftforge.network.*; + +import java.util.function.Supplier; + +@Slf4j +@Setter +@Getter +public class ResetPacket extends HandshakeMessages.C2SAcknowledge { + private int loginIndex; + public ResetPacket() { + super(); + } + public static ResetPacket decode(FriendlyByteBuf buf) { + return new ResetPacket(); + } + + public void encode(FriendlyByteBuf buf) { + } + + public static void handler(HandshakeHandler handler , ResetPacket msg, Supplier ctxSupplier) { + NetworkEvent.Context ctx = ctxSupplier.get(); + ClientResetManager.isNegotiating.set(true); + Connection conn = ctx.getNetworkManager(); + if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) { + conn.disconnect(new TranslatableComponent("ltd.mod.client.invalid_packet")); + return; + } + if (ResetHelper.clearClient(ctx)) { + NetworkHooks.registerClientLoginChannel(conn); + conn.setProtocol(ConnectionProtocol.LOGIN); + conn.setListener(new ClientHandshakePacketListenerImpl( + conn, Minecraft.getInstance(), null, s -> {} + )); + + ((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn); + + try { + ClientResetManager.handshakeChannel.reply( + new HandshakeMessages.C2SAcknowledge(), + ClientResetManager.contextConstructor.newInstance(conn, NetworkDirection.LOGIN_TO_CLIENT, 98) + ); + } catch (Exception e) { + log.error("Failed to send acknowledgment", e); + conn.disconnect(new TranslatableComponent("ltd.mod.client.error.handshake")); + } + } + ctx.setPacketHandled(true); + + } + +} + diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java new file mode 100644 index 0000000..4c37c52 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java @@ -0,0 +1,57 @@ +package com.leisuretimedock.crossmod.util; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; + +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; + +public class DebugUtils { + public static void debugBuffer(FriendlyByteBuf buf) { + int readable = buf.readableBytes(); + System.out.println("[Debug] Readable bytes: " + readable); + + if (readable <= 0) { + System.out.println("[Debug] No extra bytes to inspect."); + return; + } + + // 保存当前位置 + int index = buf.readerIndex(); + + // 读取并打印十六进制 + byte[] bytes = new byte[readable]; + buf.readBytes(bytes); + String hex = HexFormat.of().formatHex(bytes); + System.out.println("[Debug] Extra bytes (hex): " + hex); + + // 尝试以 UTF-8 解码(仅用于辅助分析) + try { + String utf8 = new String(bytes, StandardCharsets.UTF_8); + System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8); + } catch (Exception e) { + System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage()); + } + + // 还原读取位置,避免影响其他逻辑 + buf.readerIndex(index); + } + public static void debugFullBuffer(FriendlyByteBuf buf) { + ByteBuf internal = buf.copy(); // 复制整个缓冲区(包括所有字节) + int size = internal.readableBytes(); + byte[] data = new byte[size]; + internal.readBytes(data); + + System.out.println("[Debug] Full buffer size: " + size); + System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data)); + + try { + String utf8 = new String(data, StandardCharsets.UTF_8); + System.out.println("[Debug] UTF-8 decoded:\n" + utf8); + } catch (Exception e) { + System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage()); + } + + internal.release(); // 手动释放 copy() 出来的 ByteBuf,防止泄漏 + } +} diff --git a/forge-mod/src/main/resources/META-INF/accesstransformer.cfg b/forge-mod/src/main/resources/META-INF/accesstransformer.cfg index e69de29..076d79b 100644 --- a/forge-mod/src/main/resources/META-INF/accesstransformer.cfg +++ b/forge-mod/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1 @@ +public net.minecraft.client.Minecraft pendingConnection #pendingConnection diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json index ce66496..71d2dfd 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json @@ -1,4 +1,9 @@ { "ltd.mod.client.name.trans_server": "LTD Cross Server Mod", - "ltd.mod.client.key": "LTD Key" + "ltd.mod.client.key": "Open LTD Cross Server Menu", + "ltd.mod.client.negotiating": "Negotiating...", + "ltd.mod.client.failed.reset_connection": "Failed to reset connection.", + "ltd.mod.client.error.handshake": "Handshake error", + "ltd.mod.client.invalid_reset_packet": "Invalid reset packet", + "ltd.mod.client.invalid_packet": "Invalid packet" } \ No newline at end of file diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json index 14463b4..fd09310 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json @@ -1,4 +1,10 @@ { "ltd.mod.client.name.trans_server": "LTD跨服传送模组", - "ltd.mod.client.key": "LTD跨服传送按键" + "ltd.mod.client.key": "打开LTD跨服传送菜单", + "ltd.mod.client.negotiating": "重定向中 ...", + "ltd.mod.client.failed.reset_connection": "重置链接失败。", + "ltd.mod.client.error.handshake": "握手出错", + "ltd.mod.client.invalid_reset_packet": "无效的重置链接包", + "ltd.mod.client.invalid_packet": "无效的包" + } \ No newline at end of file diff --git a/forge-mod/src/main/resources/ltdcrossteleport.mixins.json b/forge-mod/src/main/resources/ltdcrossteleport.mixins.json new file mode 100644 index 0000000..ee2111c --- /dev/null +++ b/forge-mod/src/main/resources/ltdcrossteleport.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "com.leisuretimedock.crossmod.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "AccessorMinecraft", + "MixinMUINetWorkHandler", + "ModListSpoofMixin" + ], + "minVersion": "0.8" +} diff --git a/velocity-plugin/gradle.properties b/velocity-plugin/gradle.properties index 2ad4846..9944443 100644 --- a/velocity-plugin/gradle.properties +++ b/velocity-plugin/gradle.properties @@ -1,3 +1,3 @@ plugin_group=com.leisuretimedock.crossplugin -plugin_version=1.0.0.2 +plugin_version=1.0.0.5 plugin_name=CrossServerTeleport \ No newline at end of file diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java index a33e250..33852dc 100644 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java @@ -18,7 +18,7 @@ public class ReloadConfigCommand implements SimpleCommand { private final ConfigManager configManager; public static final String PERMISSION_RELOAD = "ltdcrossserver.reload"; - public static final String PERMISSION_HELP = "ltdcrossserver.help"; + public ReloadConfigCommand(ConfigManager configManager) { this.configManager = configManager; } diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java index 7bbd9d0..b2092ea 100644 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java @@ -15,6 +15,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -29,9 +30,11 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; + /** * 插件消息监听器,负责接收客户端发来的插件消息并处理跨服传送、Overlay显示等逻辑。 */ +@Slf4j public class PluginMessageListener { // 插件消息通道标识(与客户端保持一致) @@ -103,13 +106,13 @@ public class PluginMessageListener { */ private void handlePluginChannel(Player player, byte[] data) { // 简单日志,打印字节长度和十六进制,便于调试 - System.out.println("Received plugin message on channel 'channel' from player " + player.getUsername()); - System.out.println("Data length: " + data.length); + log.trace("Received plugin message on channel 'channel' from player {}", player.getUsername()); + log.trace("Data length: {}", data.length); StringBuilder sb = new StringBuilder(); for (byte b : data) { sb.append(String.format("%02X ", b)); } - System.out.println("Data hex: " + sb); + log.trace("Data hex: {}", sb); try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(data))) { String command = in.readUTF(); logger.debug("[CrossTeleportMod] Received plugin command from {}: {}", player.getUsername(), command); @@ -117,7 +120,14 @@ public class PluginMessageListener { if ("client_ready".equals(command)) { if (waitingForReady.remove(player)) { logger.debug("[CrossTeleportMod] {} is ready, sending overlay", player.getUsername()); - OverlayManager.showOverlay(player); + player.getCurrentServer().ifPresent(i -> { + String name = i.getServerInfo().getName(); + boolean contains = configManager.getOverlayServers().contains(name); + if (contains) { + OverlayManager.showOverlay(player); + } + else OverlayManager.hideOverlay(player); + }); // TODO: 支持发送自定义服务器列表 } else { logger.debug("[CrossTeleportMod] Received client_ready from {}, but not in waiting set", player.getUsername()); @@ -171,13 +181,7 @@ public class PluginMessageListener { logger.debug("[CrossTeleportMod] Player {} joined server {}", player.getUsername(), currentServer); - if (configManager.getOverlayServers().contains(currentServer)) { - waitingForReady.add(player); - logger.debug("[CrossTeleportMod] Added {} to waitingForReady set", player.getUsername()); - } else { - OverlayManager.hideOverlay(player); - logger.debug("[CrossTeleportMod] Hiding overlay for {}", player.getUsername()); - } + waitingForReady.add(player); } /**