From 7cd9f75579b449d89e5533b3f5af851ed61e2e59 Mon Sep 17 00:00:00 2001 From: plsvivo50 <165546565+plsvivo50@users.noreply.github.com> Date: Sat, 9 May 2026 00:31:32 +0800 Subject: [PATCH 1/3] Update ChatImagePacket.java --- .../chatimage/network/ChatImagePacket.java | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/forge/origin/src/main/java/io/github/kituin/chatimage/network/ChatImagePacket.java b/forge/origin/src/main/java/io/github/kituin/chatimage/network/ChatImagePacket.java index e46fdd5f..9a343f27 100644 --- a/forge/origin/src/main/java/io/github/kituin/chatimage/network/ChatImagePacket.java +++ b/forge/origin/src/main/java/io/github/kituin/chatimage/network/ChatImagePacket.java @@ -4,9 +4,9 @@ import com.google.gson.Gson; import io.github.kituin.ChatImageCode.ChatImageFrame; import io.github.kituin.ChatImageCode.ChatImageIndex; +import io.github.kituin.ChatImageCode.NetworkHelper; import net.minecraft.client.Minecraft; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,7 +22,6 @@ public class ChatImagePacket { public static Gson gson = new Gson(); - /** * 发送给服务器一连串网络包(异步) * 文件频道 @@ -44,17 +43,15 @@ public static void loadFromServer(String url) { if (Minecraft.getInstance().player != null) { FileInfoChannel.sendToServer(new FileInfoChannelPacket(url)); LOGGER.info("[try get from server]" + url); - } else { AddImageError(url, ChatImageFrame.FrameError.FILE_NOT_FOUND); } } - /** * 服务端接收 图片文件分块 的处理 * - * @param player #ServerPlayer# + * @param player net.minecraft.server.level.ServerPlayer * @param res String */ public static void serverFileChannelReceived(#ServerPlayer# player, String res) { @@ -90,7 +87,6 @@ public static void clientDownloadFileChannelReceived(String res) { } } - public static void clientFileInfoChannelReceived(String data) { String url = data.substring(6); LOGGER.info(url); @@ -105,8 +101,19 @@ public static void clientFileInfoChannelReceived(String data) { public static void serverFileInfoChannelReceived(#ServerPlayer# player, String url) { HashMap list = SERVER_BLOCK_CACHE.getBlock(url); + + // ========== 修复:同时支持 file:// URI 和本地绝对路径 ========== + if (list == null) { + boolean isLocalPath = url.startsWith("file://") + || (url.length() > 1 && url.charAt(1) == ':') // Windows: D:... + || url.startsWith("/"); // Linux: /home/... + if (isLocalPath) { + list = loadLocalFileToCache(url); + } + } + // ============================================================ + if (list != null) { - // 服务器存在缓存图片,直接发送给客户端 for (Map.Entry entry : list.entrySet()) { LOGGER.debug("[GetFileChannel->Client:{}/{}]{}", entry.getKey(), list.size() - 1, url); DownloadFileChannel.sendToPlayer(new DownloadFileChannelPacket(entry.getValue()), player); @@ -117,11 +124,56 @@ public static void serverFileInfoChannelReceived(#ServerPlayer# player, String u // 通知客户端无文件 FileBackChannel.sendToPlayer(new FileInfoChannelPacket("null->" + url), player); LOGGER.error("[GetFileChannel]not found in server:{}", url); - // 记录uuid,后续有文件了推送 if (player != null) { SERVER_BLOCK_CACHE.tryAddUser(url, player.getStringUUID()); } LOGGER.info("[GetFileChannel]记录uuid:{}", player.getStringUUID()); LOGGER.info("[not found in server]{}", url); } -} \ No newline at end of file + + /** + * 读取本地文件(支持 file:// URI 和纯本地路径),使用 ChatImage 标准分包逻辑塞入 ServerBlockCache + */ + private static HashMap loadLocalFileToCache(String fileUrl) { + try { + java.io.File file; + + // 区分 file:// URI 和纯本地路径 + if (fileUrl.startsWith("file://")) { + java.net.URI uri = new java.net.URI(fileUrl); + java.nio.file.Path path = java.nio.file.Paths.get(uri); + file = path.toFile(); + } else { + file = new java.io.File(fileUrl); + } + + if (!file.exists()) { + LOGGER.error("[FileFallback]本地文件不存在:{}", file.getAbsolutePath()); + return null; + } + + // 使用 NetworkHelper 的标准分包逻辑(自动处理大文件分包) + List packets = NetworkHelper.createFilePacket(fileUrl, file); + if (packets.isEmpty()) { + LOGGER.error("[FileFallback]文件分包失败:{}", fileUrl); + return null; + } + + // 将分包塞入 ServerBlockCache + for (String packet : packets) { + ChatImageIndex index = gson.fromJson(packet, ChatImageIndex.class); + SERVER_BLOCK_CACHE.createBlock(index, packet); + } + + LOGGER.info("[FileFallback]本地文件已加载到缓存:{} ({} bytes, {} packets)", + fileUrl, file.length(), packets.size()); + + // 返回完整的块列表 + return SERVER_BLOCK_CACHE.getBlock(fileUrl); + + } catch (Exception e) { + LOGGER.error("[FileFallback]加载本地文件失败:{} - {}", fileUrl, e.getLocalizedMessage()); + return null; + } + } +} From 7905e4fa3b52b99e7a9b111af2f9e9edc8afb917 Mon Sep 17 00:00:00 2001 From: plsvivo50 <165546565+plsvivo50@users.noreply.github.com> Date: Sat, 9 May 2026 00:32:19 +0800 Subject: [PATCH 2/3] Update ChatImageClientAdapter.java --- .../integration/ChatImageClientAdapter.java | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/forge/origin/src/main/java/io/github/kituin/chatimage/integration/ChatImageClientAdapter.java b/forge/origin/src/main/java/io/github/kituin/chatimage/integration/ChatImageClientAdapter.java index e2b0c0e0..c78bbe4b 100644 --- a/forge/origin/src/main/java/io/github/kituin/chatimage/integration/ChatImageClientAdapter.java +++ b/forge/origin/src/main/java/io/github/kituin/chatimage/integration/ChatImageClientAdapter.java @@ -5,6 +5,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -25,7 +26,10 @@ public int getTimeOut() { @Override public ChatImageFrame.TextureReader<#ResourceLocation#> loadTexture(InputStream image) throws IOException { - #NativeImage# nativeImage = #NativeImage#.read(image); + #NativeImage# nativeImage = loadNativeImage(image); + if (nativeImage == null) { + throw new IOException("Failed to load image into NativeImage"); + } return new ChatImageFrame.TextureReader<>( Minecraft.getInstance().getTextureManager().register(MOD_ID + "/chatimage", new DynamicTexture(nativeImage)), @@ -34,6 +38,46 @@ public int getTimeOut() { ); } + /** + * 兼容加载各种格式的图片为 NativeImage + */ + private static #NativeImage# loadNativeImage(InputStream image) throws IOException { + #NativeImage# nativeImage = null; + + // 先尝试标准 NativeImage.read() + try { + nativeImage = #NativeImage#.read(image); + if (nativeImage != null) return nativeImage; + } catch (Exception ignored) { + } + + // 失败时尝试用 ImageIO 中转转换 + try { + image.reset(); + java.awt.image.BufferedImage bufImage = javax.imageio.ImageIO.read(image); + if (bufImage == null) return null; + + // 转换为标准 ARGB 格式 + java.awt.image.BufferedImage rgbImage = new java.awt.image.BufferedImage( + bufImage.getWidth(), bufImage.getHeight(), + java.awt.image.BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics2D g = rgbImage.createGraphics(); + g.drawImage(bufImage, 0, 0, null); + g.dispose(); + + // 写出为 PNG 再读入 NativeImage + java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); + if (javax.imageio.ImageIO.write(rgbImage, "png", baos)) { + nativeImage = #NativeImage#.read( + new ByteArrayInputStream(baos.toByteArray())); + } + } catch (Exception e) { + return nativeImage; + } + + return nativeImage; + } + @Override public void sendToServer(String url, File file, boolean isToServer) { if (isToServer) { @@ -58,7 +102,7 @@ public int getMaxFileSize() { } @Override - public #MutableComponent# getProcessMessage(int i) { + public net.minecraft.network.chat.MutableComponent getProcessMessage(int i) { return createTranslatableComponent("process.chatimage.message", i); } -} \ No newline at end of file +} From 0dc1c2a27a8826067d5904ff2c22d1f809e26642 Mon Sep 17 00:00:00 2001 From: plsvivo50 <165546565+plsvivo50@users.noreply.github.com> Date: Sat, 9 May 2026 00:32:40 +0800 Subject: [PATCH 3/3] Update ChatImage.java --- .../io/github/kituin/chatimage/ChatImage.java | 129 ++++++++++-------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/forge/origin/src/main/java/io/github/kituin/chatimage/ChatImage.java b/forge/origin/src/main/java/io/github/kituin/chatimage/ChatImage.java index 1a8457c9..5b0c4109 100644 --- a/forge/origin/src/main/java/io/github/kituin/chatimage/ChatImage.java +++ b/forge/origin/src/main/java/io/github/kituin/chatimage/ChatImage.java @@ -15,10 +15,13 @@ import io.github.kituin.chatimage.network.FileInfoChannel; import io.github.kituin.ChatImageCode.ChatImageCodeInstance; import io.github.kituin.ChatImageCode.ChatImageConfig; +import io.github.kituin.ChatImageCode.ChatImageFrame; +import io.github.kituin.ChatImageCode.ClientStorage; import net.minecraft.client.Minecraft; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -32,16 +35,10 @@ import org.apache.logging.log4j.Logger; import java.io.File; -// IF > forge-1.18.2 -//import net.minecraftforge.client.event.RegisterKeyMappingsEvent; -// END IF -// IF forge-1.16.5 -//import net.minecraftforge.fml.ExtensionPoint; -// ELSE -//import net.minecraft.commands.Commands; -//import net.minecraftforge.client.event.RegisterClientCommandsEvent; -//import net.minecraftforge.event.server.ServerStartingEvent; -// END IF +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraft.commands.Commands; +import net.minecraftforge.client.event.RegisterClientCommandsEvent; +import net.minecraftforge.event.server.ServerStartingEvent; import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; @@ -77,60 +74,76 @@ public static class ClientModEvents { CONFIG = ChatImageConfig.loadConfig(); ChatImageCodeInstance.CLIENT_ADAPTER = new ChatImageClientAdapter(); } -// IF >= forge-1.19 -// @SubscribeEvent -// public static void onKeyBindRegister(RegisterKeyMappingsEvent event) { -// KeyBindings.init(event); -// LOGGER.info("KeyBindings Register"); -// } -// END IF + + @SubscribeEvent + public static void onKeyBindRegister(RegisterKeyMappingsEvent event) { + KeyBindings.init(event); + LOGGER.info("KeyBindings Register"); + } @SubscribeEvent public static void onClientSetup(FMLClientSetupEvent event) { LOGGER.info("[ChatImage]Client start"); -// IF >= forge-1.16 && < forge-1.19 -// KeyBindings.init(); -// LOGGER.info("KeyBindings Register"); -// END IF -// IF forge-1.16.5 -// ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, -// () -> (mc, screen) -> new ConfigScreen(screen)); -// ELSE -// ModLoadingContext.get().registerExtensionPoint(#ConfigScreenFactory#.class, () -> new #ConfigScreenFactory#((minecraft, screen) -> new ConfigScreen(screen))); -// MinecraftForge.EVENT_BUS.addListener(ClientModEvents::onClientCommand); -// END IF + ModLoadingContext.get().registerExtensionPoint(net.minecraftforge.client.ConfigScreenHandler.ConfigScreenFactory.class, () -> new net.minecraftforge.client.ConfigScreenHandler.ConfigScreenFactory((minecraft, screen) -> new ConfigScreen(screen))); + MinecraftForge.EVENT_BUS.addListener(ClientModEvents::onClientCommand); MinecraftForge.EVENT_BUS.addListener(ClientModEvents::onKeyInput); MinecraftForge.EVENT_BUS.addListener(ClientModEvents::onClientStaring); + + // ========== 新增:注册GIF动画Tick更新 ========== + MinecraftForge.EVENT_BUS.addListener(ClientModEvents::onClientTick); + LOGGER.info("[ChatImage]GIF动画Tick已注册"); + // ============================================== + } + + // ========== 新增:客户端Tick事件,驱动GIF动画(使用反射访问ClientStorage私有字段) ========== + public static void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.END) { + try { + java.lang.reflect.Field imagesField = ClientStorage.class.getDeclaredField("images"); + imagesField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.HashMap images = + (java.util.HashMap) imagesField.get(null); + + for (ChatImageFrame frame : images.values()) { + if (frame != null && frame.getSiblings().size() > 0) { + frame.gifLoop(2); + } + } + } catch (Exception e) { + // 静默失败,不影响正常功能 + } + } } -// IF > forge-1.16.5 -// public static void onClientCommand(RegisterClientCommandsEvent event) { -// CommandDispatcher<#CommandSourceStack#> dispatcher = event.getDispatcher(); -// LiteralCommandNode<#CommandSourceStack#> cmd = dispatcher.register( -// Commands.literal(MOD_ID) -// .then(Commands.literal("send") -// .then(Commands.argument("name", StringArgumentType.string()) -// .then(Commands.argument("url", greedyString()) -// .executes(SendChatImage.instance) -// ) -// ) -// ) -// .then(Commands.literal("url") -// .then(Commands.argument("url", greedyString()) -// .executes(SendChatImage.instance) -// ) -// ) -// .then(Commands.literal("help") -// .executes(Help.instance) -// ) -// .then(Commands.literal("reload") -// .executes(ReloadConfig.instance) -// ) -// -// ); -// } -// END IF - - public static void onKeyInput(#InputEvent.Key# event) { + // ======================================================================================== + + public static void onClientCommand(RegisterClientCommandsEvent event) { + CommandDispatcher dispatcher = event.getDispatcher(); + LiteralCommandNode cmd = dispatcher.register( + Commands.literal(MOD_ID) + .then(Commands.literal("send") + .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.argument("url", greedyString()) + .executes(SendChatImage.instance) + ) + ) + ) + .then(Commands.literal("url") + .then(Commands.argument("url", greedyString()) + .executes(SendChatImage.instance) + ) + ) + .then(Commands.literal("help") + .executes(Help.instance) + ) + .then(Commands.literal("reload") + .executes(ReloadConfig.instance) + ) + + ); + } + + public static void onKeyInput(net.minecraftforge.client.event.InputEvent.Key event) { if (KeyBindings.gatherManaKeyMapping.consumeClick()) { Minecraft.getInstance().setScreen(new ConfigScreen(Minecraft.getInstance().screen)); } @@ -139,4 +152,4 @@ public static void onKeyInput(#InputEvent.Key# event) { public static void onClientStaring(RegisterCommandsEvent event) { } } -} \ No newline at end of file +}