From d5bfd69e35b21f701390a8c4c4c58d7ec1fff1fc Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 6 Jan 2014 23:48:53 -0500 Subject: [PATCH] Fix problem with connecting to vanilla. SHOW what's modded and what's vanilla in the list. Hooks that make the blocking work to come --- .../net/minecraft/client/Minecraft.java.patch | 12 +- .../client/gui/GuiMultiplayer.java.patch | 18 +++ .../gui/ServerListEntryNormal.java.patch | 31 ++++++ .../client/network/OldServerPinger.java.patch | 18 +++ .../network/ServerStatusResponse.java.patch | 27 +++++ .../fml/client/ExtendedServerListData.java | 16 +++ .../cpw/mods/fml/client/FMLClientHandler.java | 103 ++++++++++++++++++ .../fml/common/network/FMLNetworkEvent.java | 8 +- .../handshake/FMLHandshakeClientState.java | 11 +- .../network/handshake/NetworkDispatcher.java | 41 +++++-- .../network/internal/FMLNetworkHandler.java | 19 ++++ .../assets/fml/textures/gui/icons.png | Bin 0 -> 5218 bytes 12 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 patches/minecraft/net/minecraft/client/gui/GuiMultiplayer.java.patch create mode 100644 patches/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java.patch create mode 100644 patches/minecraft/net/minecraft/client/network/OldServerPinger.java.patch create mode 100644 patches/minecraft/net/minecraft/network/ServerStatusResponse.java.patch create mode 100644 src/main/java/cpw/mods/fml/client/ExtendedServerListData.java create mode 100644 src/main/resources/assets/fml/textures/gui/icons.png diff --git a/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 80ffafed92..169f32d7e6 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -44,7 +44,7 @@ Display.setVSyncEnabled(this.field_71474_y.field_74352_v); } -@@ -915,9 +922,11 @@ +@@ -916,9 +923,11 @@ if (!this.field_71454_w) { @@ -56,7 +56,7 @@ } GL11.glFlush(); -@@ -1495,6 +1504,8 @@ +@@ -1496,6 +1505,8 @@ --this.field_71467_ac; } @@ -65,7 +65,7 @@ this.field_71424_I.func_76320_a("gui"); if (!this.field_71445_n) -@@ -1645,6 +1656,7 @@ +@@ -1646,6 +1657,7 @@ this.field_71462_r.func_146274_d(); } } @@ -73,7 +73,7 @@ } if (this.field_71429_W > 0) -@@ -1786,6 +1798,7 @@ +@@ -1787,6 +1799,7 @@ } } } @@ -81,7 +81,7 @@ } } -@@ -1977,6 +1990,8 @@ +@@ -1978,6 +1991,8 @@ this.field_71453_ak.func_74428_b(); } @@ -90,7 +90,7 @@ this.field_71424_I.func_76319_b(); this.field_71423_H = func_71386_F(); } -@@ -2093,6 +2108,7 @@ +@@ -2094,6 +2109,7 @@ this.field_110448_aq.func_148529_f(); this.func_71351_a((ServerData)null); this.field_71455_al = false; diff --git a/patches/minecraft/net/minecraft/client/gui/GuiMultiplayer.java.patch b/patches/minecraft/net/minecraft/client/gui/GuiMultiplayer.java.patch new file mode 100644 index 0000000000..43b0b5b4b4 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/gui/GuiMultiplayer.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/client/gui/GuiMultiplayer.java ++++ ../src-work/minecraft/net/minecraft/client/gui/GuiMultiplayer.java +@@ -2,6 +2,7 @@ + + import com.google.common.base.Splitter; + import com.google.common.collect.Lists; ++import cpw.mods.fml.client.FMLClientHandler; + import cpw.mods.fml.relauncher.Side; + import cpw.mods.fml.relauncher.SideOnly; + import java.util.List; +@@ -40,6 +41,7 @@ + public GuiMultiplayer(GuiScreen p_i1040_1_) + { + this.field_146798_g = p_i1040_1_; ++ FMLClientHandler.instance().setupServerList(); + } + + public void func_73866_w_() diff --git a/patches/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java.patch b/patches/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java.patch new file mode 100644 index 0000000000..e8ae64aa35 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java.patch @@ -0,0 +1,31 @@ +--- ../src-base/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java ++++ ../src-work/minecraft/net/minecraft/client/gui/ServerListEntryNormal.java +@@ -2,6 +2,7 @@ + + import com.google.common.base.Charsets; + import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import cpw.mods.fml.client.FMLClientHandler; + import cpw.mods.fml.relauncher.Side; + import cpw.mods.fml.relauncher.SideOnly; + import io.netty.buffer.ByteBuf; +@@ -83,7 +84,7 @@ + boolean flag2 = this.field_148301_e.field_82821_f < 4; + boolean flag3 = flag1 || flag2; + this.field_148300_d.field_71466_p.func_78276_b(this.field_148301_e.field_78847_a, p_148279_2_ + 32 + 3, p_148279_3_ + 1, 16777215); +- List list = this.field_148300_d.field_71466_p.func_78271_c(this.field_148301_e.field_78843_d, p_148279_4_ - 32 - 2); ++ List list = this.field_148300_d.field_71466_p.func_78271_c(FMLClientHandler.instance().fixDescription(this.field_148301_e.field_78843_d), p_148279_4_ - 32 - 2); + + for (int l1 = 0; l1 < Math.min(list.size(), 2); ++l1) + { +@@ -174,6 +175,11 @@ + int k2 = p_148279_7_ - p_148279_2_; + int l2 = p_148279_8_ - p_148279_3_; + ++ String tooltip = FMLClientHandler.instance().enhanceServerListEntry(this, this.field_148301_e, p_148279_2_, p_148279_4_, p_148279_3_, k2, l2); ++ if (tooltip != null) ++ { ++ this.field_148303_c.func_146793_a(tooltip); ++ } else + if (k2 >= p_148279_4_ - 15 && k2 <= p_148279_4_ - 5 && l2 >= 0 && l2 <= 8) + { + this.field_148303_c.func_146793_a(s1); diff --git a/patches/minecraft/net/minecraft/client/network/OldServerPinger.java.patch b/patches/minecraft/net/minecraft/client/network/OldServerPinger.java.patch new file mode 100644 index 0000000000..22e10b3f43 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/network/OldServerPinger.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/client/network/OldServerPinger.java ++++ ../src-work/minecraft/net/minecraft/client/network/OldServerPinger.java +@@ -4,6 +4,7 @@ + import com.google.common.base.Splitter; + import com.google.common.collect.Iterables; + import com.mojang.authlib.GameProfile; ++import cpw.mods.fml.client.FMLClientHandler; + import cpw.mods.fml.relauncher.Side; + import cpw.mods.fml.relauncher.SideOnly; + import io.netty.bootstrap.Bootstrap; +@@ -148,6 +149,7 @@ + p_147224_1_.func_147407_a((String)null); + } + ++ FMLClientHandler.instance().bindServerListData(p_147224_1_, serverstatusresponse); + networkmanager.func_150725_a(new C01PacketPing(Minecraft.func_71386_F()), new GenericFutureListener[0]); + this.field_147403_d = true; + } diff --git a/patches/minecraft/net/minecraft/network/ServerStatusResponse.java.patch b/patches/minecraft/net/minecraft/network/ServerStatusResponse.java.patch new file mode 100644 index 0000000000..cc8f10c8ff --- /dev/null +++ b/patches/minecraft/net/minecraft/network/ServerStatusResponse.java.patch @@ -0,0 +1,27 @@ +--- ../src-base/minecraft/net/minecraft/network/ServerStatusResponse.java ++++ ../src-work/minecraft/net/minecraft/network/ServerStatusResponse.java +@@ -8,6 +8,8 @@ + import com.google.gson.JsonSerializationContext; + import com.google.gson.JsonSerializer; + import com.mojang.authlib.GameProfile; ++import cpw.mods.fml.client.FMLClientHandler; ++import cpw.mods.fml.common.network.internal.FMLNetworkHandler; + import java.lang.reflect.Type; + import net.minecraft.util.IChatComponent; + import net.minecraft.util.JsonUtils; +@@ -230,6 +232,7 @@ + serverstatusresponse.func_151320_a(JsonUtils.func_151200_h(jsonobject, "favicon")); + } + ++ FMLClientHandler.instance().captureAdditionalData(serverstatusresponse, jsonobject); + return serverstatusresponse; + } + +@@ -257,6 +260,7 @@ + jsonobject.addProperty("favicon", p_151313_1_.func_151316_d()); + } + ++ FMLNetworkHandler.enhanceStatusQuery(jsonobject); + return jsonobject; + } + diff --git a/src/main/java/cpw/mods/fml/client/ExtendedServerListData.java b/src/main/java/cpw/mods/fml/client/ExtendedServerListData.java new file mode 100644 index 0000000000..1253e4c768 --- /dev/null +++ b/src/main/java/cpw/mods/fml/client/ExtendedServerListData.java @@ -0,0 +1,16 @@ +package cpw.mods.fml.client; + +public class ExtendedServerListData { + public final String type; + public final boolean isCompatible; + public final int modCount; + public final boolean isBlocked; + + public ExtendedServerListData(String type, boolean isCompatible, int modCount, boolean isBlocked) + { + this.type = type; + this.isCompatible = isCompatible; + this.modCount = modCount; + this.isBlocked = isBlocked; + } +} diff --git a/src/main/java/cpw/mods/fml/client/FMLClientHandler.java b/src/main/java/cpw/mods/fml/client/FMLClientHandler.java index 40366bcf52..1b1688a2ce 100644 --- a/src/main/java/cpw/mods/fml/client/FMLClientHandler.java +++ b/src/main/java/cpw/mods/fml/client/FMLClientHandler.java @@ -14,15 +14,19 @@ package cpw.mods.fml.client; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiIngameMenu; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiSelectWorld; +import net.minecraft.client.gui.ServerListEntryNormal; +import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.renderer.entity.Render; import net.minecraft.client.renderer.entity.RenderManager; @@ -34,7 +38,9 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.launchwrapper.Launch; import net.minecraft.network.INetHandler; import net.minecraft.network.NetworkManager; +import net.minecraft.network.ServerStatusResponse; import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; import org.apache.logging.log4j.Level; import com.google.common.base.Strings; import com.google.common.base.Throwables; @@ -43,6 +49,7 @@ import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.gson.JsonObject; import cpw.mods.fml.client.registry.RenderingRegistry; import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.DuplicateModsFoundException; @@ -124,6 +131,9 @@ public class FMLClientHandler implements IFMLSidedHandler private BiMap guiFactories; + private Map extraServerListData; + private Map serverDataTag; + /** * Called to start the whole game off * @@ -544,4 +554,97 @@ public class FMLClientHandler implements IFMLSidedHandler { return guiFactories.get(selectedMod); } + + + public void setupServerList() + { + extraServerListData = Collections.synchronizedMap(Maps.newHashMap()); + serverDataTag = Collections.synchronizedMap(Maps.newHashMap()); + } + + public void captureAdditionalData(ServerStatusResponse serverstatusresponse, JsonObject jsonobject) + { + if (jsonobject.has("modinfo")) + { + JsonObject fmlData = jsonobject.get("modinfo").getAsJsonObject(); + extraServerListData.put(serverstatusresponse, fmlData); + } + } + public void bindServerListData(ServerData data, ServerStatusResponse originalResponse) + { + if (extraServerListData.containsKey(originalResponse)) + { + JsonObject jsonData = extraServerListData.get(originalResponse); + String type = jsonData.get("type").getAsString(); + int modCount = jsonData.get("modList").getAsJsonArray().size(); + boolean moddedClientAllowed = jsonData.has("clientModsAllowed") ? jsonData.get("clientModsAllowed").getAsBoolean() : true; + serverDataTag.put(data, new ExtendedServerListData(type, true, modCount, !moddedClientAllowed)); + } + else + { + String serverDescription = data.field_78843_d; + boolean moddedClientAllowed = true; + if (!Strings.isNullOrEmpty(serverDescription)) + { + moddedClientAllowed = !serverDescription.endsWith(":NOFML§r"); + } + serverDataTag.put(data, new ExtendedServerListData("VANILLA", false, -1, !moddedClientAllowed)); + } + } + + private static final ResourceLocation iconSheet = new ResourceLocation("fml:textures/gui/icons.png"); + + public String enhanceServerListEntry(ServerListEntryNormal serverListEntry, ServerData serverEntry, int x, int width, int y, int relativeMouseX, int relativeMouseY) + { + String tooltip; + int idx; + boolean blocked = false; + if (serverDataTag.containsKey(serverEntry)) + { + ExtendedServerListData extendedData = serverDataTag.get(serverEntry); + if ("FML".equals(extendedData.type) && extendedData.isCompatible) + { + idx = 0; + tooltip = String.format("Compatible FML modded server\n%d mods present", extendedData.modCount); + } + else if ("FML".equals(extendedData.type) && !extendedData.isCompatible) + { + idx = 16; + tooltip = String.format("Incompatible FML modded server\n%d mods present", extendedData.modCount); + } + else if ("BUKKIT".equals(extendedData.type)) + { + idx = 32; + tooltip = String.format("Bukkit modded server"); + } + else if ("VANILLA".equals(extendedData.type)) + { + idx = 48; + tooltip = String.format("Vanilla server"); + } + else + { + idx = 64; + tooltip = String.format("Unknown server data"); + } + blocked = extendedData.isBlocked; + } + else + { + return null; + } + this.client.func_110434_K().func_110577_a(iconSheet); + Gui.func_146110_a(x + width - 18, y + 10, 0, (float)idx, 16, 16, 256.0f, 256.0f); + if (blocked) + { + Gui.func_146110_a(x + width - 18, y + 10, 0, 80, 16, 16, 256.0f, 256.0f); + } + + return relativeMouseX > width - 15 && relativeMouseX < width && relativeMouseY > 10 && relativeMouseY < 26 ? tooltip : null; + } + + public String fixDescription(String description) + { + return description.endsWith(":NOFML§r") ? description.substring(0, description.length() - 8)+"§r" : description; + } } diff --git a/src/main/java/cpw/mods/fml/common/network/FMLNetworkEvent.java b/src/main/java/cpw/mods/fml/common/network/FMLNetworkEvent.java index cf721e3def..652396fee4 100644 --- a/src/main/java/cpw/mods/fml/common/network/FMLNetworkEvent.java +++ b/src/main/java/cpw/mods/fml/common/network/FMLNetworkEvent.java @@ -20,10 +20,12 @@ public class FMLNetworkEvent extends Event { } public static class ClientConnectedToServerEvent extends FMLNetworkEvent { public final boolean isLocal; - public ClientConnectedToServerEvent(NetworkManager manager) + public final String connectionType; + public ClientConnectedToServerEvent(NetworkManager manager, String connectionType) { super((INetHandlerPlayClient) manager.func_150729_e(), INetHandlerPlayClient.class, manager); - isLocal = manager.func_150731_c(); + this.isLocal = manager.func_150731_c(); + this.connectionType = connectionType; } } @@ -32,7 +34,7 @@ public class FMLNetworkEvent extends Event { public ServerConnectionFromClientEvent(NetworkManager manager) { super((INetHandlerPlayServer) manager.func_150729_e(), INetHandlerPlayServer.class, manager); - isLocal = manager.func_150731_c(); + this.isLocal = manager.func_150731_c(); } } public static class ServerDisconnectionFromClientEvent extends FMLNetworkEvent { diff --git a/src/main/java/cpw/mods/fml/common/network/handshake/FMLHandshakeClientState.java b/src/main/java/cpw/mods/fml/common/network/handshake/FMLHandshakeClientState.java index 30940037ad..bd3df960b5 100644 --- a/src/main/java/cpw/mods/fml/common/network/handshake/FMLHandshakeClientState.java +++ b/src/main/java/cpw/mods/fml/common/network/handshake/FMLHandshakeClientState.java @@ -37,8 +37,17 @@ enum FMLHandshakeClientState implements IHandshakeState @Override public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg) { - FMLLog.info("Server protocol version %x", ((FMLHandshakeMessage.ServerHello)msg).protocolVersion()); + // write our custom packet registration, always ctx.writeAndFlush(FMLHandshakeMessage.makeCustomChannelRegistration(NetworkRegistry.INSTANCE.channelNamesFor(Side.CLIENT))); + if (msg == null) + { + NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get(); + dispatcher.abortClientHandshake("VANILLA"); + // VANILLA login + return DONE; + } + + FMLLog.info("Server protocol version %x", ((FMLHandshakeMessage.ServerHello)msg).protocolVersion()); ctx.writeAndFlush(new FMLHandshakeMessage.ClientHello()); ctx.writeAndFlush(new FMLHandshakeMessage.ModList(Loader.instance().getActiveModList())); return WAITINGSERVERDATA; diff --git a/src/main/java/cpw/mods/fml/common/network/handshake/NetworkDispatcher.java b/src/main/java/cpw/mods/fml/common/network/handshake/NetworkDispatcher.java index 7e00a9e293..07c2cd8e03 100644 --- a/src/main/java/cpw/mods/fml/common/network/handshake/NetworkDispatcher.java +++ b/src/main/java/cpw/mods/fml/common/network/handshake/NetworkDispatcher.java @@ -12,6 +12,7 @@ import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.ScheduledFuture; import java.net.SocketAddress; import java.util.List; +import java.util.Locale; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import net.minecraft.entity.player.EntityPlayerMP; @@ -21,6 +22,7 @@ import net.minecraft.network.NetHandlerPlayServer; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.play.client.C17PacketCustomPayload; +import net.minecraft.network.play.server.S01PacketJoinGame; import net.minecraft.network.play.server.S3FPacketCustomPayload; import net.minecraft.network.play.server.S40PacketDisconnect; import net.minecraft.server.management.ServerConfigurationManager; @@ -69,7 +71,6 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple private final ServerConfigurationManager scm; private EntityPlayerMP player; private ConnectionState state; - @SuppressWarnings("unused") private ConnectionType connectionType; private final Side side; private final EmbeddedChannel handshakeChannel; @@ -151,19 +152,19 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple this.state = ConnectionState.AWAITING_HANDSHAKE; } - private void completeClientSideConnection() + private void completeClientSideConnection(ConnectionType type) { - FMLLog.info("[%s] Client side modded connection established", Thread.currentThread().getName()); + this.connectionType = type; + FMLLog.info("[%s] Client side %s connection established", Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH)); this.state = ConnectionState.CONNECTED; - this.connectionType = ConnectionType.MODDED; - FMLCommonHandler.instance().bus().post(new FMLNetworkEvent.ClientConnectedToServerEvent(manager)); + FMLCommonHandler.instance().bus().post(new FMLNetworkEvent.ClientConnectedToServerEvent(manager, this.connectionType.name())); } - private void completeServerSideConnection() + private void completeServerSideConnection(ConnectionType type) { - FMLLog.info("[%s] Server side modded connection established", Thread.currentThread().getName()); + this.connectionType = type; + FMLLog.info("[%s] Server side %s connection established", Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH)); this.state = ConnectionState.CONNECTED; - this.connectionType = ConnectionType.MODDED; FMLCommonHandler.instance().bus().post(new FMLNetworkEvent.ServerConnectionFromClientEvent(manager)); scm.func_72355_a(manager, player, serverHandler); } @@ -181,7 +182,7 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple } else if (state != ConnectionState.CONNECTED && state != ConnectionState.HANDSHAKECOMPLETE) { - FMLLog.info("Unexpected packet during modded negotiation - assuming vanilla or keepalives : %s", msg.getClass().getName()); + handled = handleVanilla(msg); } if (!handled) { @@ -189,6 +190,19 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple } } + private boolean handleVanilla(Packet msg) + { + if (state == ConnectionState.AWAITING_HANDSHAKE && msg instanceof S01PacketJoinGame) + { + handshakeChannel.pipeline().fireUserEventTriggered(msg); + } + else + { + FMLLog.info("Unexpected packet during modded negotiation - assuming vanilla or keepalives : %s", msg.getClass().getName()); + } + return false; + } + public INetHandler getNetHandler() { return netHandler; @@ -399,11 +413,11 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple } if (side == Side.CLIENT) { - completeClientSideConnection(); + completeClientSideConnection(ConnectionType.MODDED); } else { - completeServerSideConnection(); + completeServerSideConnection(ConnectionType.MODDED); } } @@ -411,4 +425,9 @@ public class NetworkDispatcher extends SimpleChannelInboundHandler imple { state = ConnectionState.HANDSHAKECOMPLETE; } + + public void abortClientHandshake(String type) + { + completeClientSideConnection(ConnectionType.valueOf(type)); + } } diff --git a/src/main/java/cpw/mods/fml/common/network/internal/FMLNetworkHandler.java b/src/main/java/cpw/mods/fml/common/network/internal/FMLNetworkHandler.java index 3f291671d6..39515032b8 100644 --- a/src/main/java/cpw/mods/fml/common/network/internal/FMLNetworkHandler.java +++ b/src/main/java/cpw/mods/fml/common/network/internal/FMLNetworkHandler.java @@ -29,9 +29,12 @@ import net.minecraft.server.management.ServerConfigurationManager; import net.minecraft.world.World; import org.apache.logging.log4j.core.helpers.Integers; import com.google.common.collect.Lists; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLContainer; import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.network.FMLEmbeddedChannel; import cpw.mods.fml.common.network.FMLOutboundHandler; @@ -176,4 +179,20 @@ public class FMLNetworkHandler return list; } + + public static void enhanceStatusQuery(JsonObject jsonobject) + { + JsonObject fmlData = new JsonObject(); + fmlData.addProperty("type", "FML"); + JsonArray modList = new JsonArray(); + for (ModContainer mc : Loader.instance().getActiveModList()) + { + JsonObject modData = new JsonObject(); + modData.addProperty("modid", mc.getModId()); + modData.addProperty("version", mc.getVersion()); + modList.add(modData); + } + fmlData.add("modList", modList); + jsonobject.add("modinfo", fmlData); + } } diff --git a/src/main/resources/assets/fml/textures/gui/icons.png b/src/main/resources/assets/fml/textures/gui/icons.png new file mode 100644 index 0000000000000000000000000000000000000000..20bb91e9d60660951066e58ec56a64a0c8f1190b GIT binary patch literal 5218 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 literal 0 HcmV?d00001