diff --git a/src/minecraft/Start.java b/src/minecraft/Start.java new file mode 100644 index 0000000..ac40620 --- /dev/null +++ b/src/minecraft/Start.java @@ -0,0 +1,88 @@ +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; + +import net.minecraft.client.Minecraft; + +public class Start +{ + public static void main(String[] args) + { + try + { + Field f = Minecraft.class.getDeclaredField("minecraftDir"); + Field.setAccessible(new Field[] { f }, true); + f.set(null, new File(".")); + } + catch (Exception e) + { + e.printStackTrace(); + return; + } + + if (args.length != 2) + { + Minecraft.main(args); + } + else + { + try + { + String parameters = "http://login.minecraft.net/?user=" + URLEncoder.encode(args[0], "UTF-8") + + "&password=" + URLEncoder.encode(args[1], "UTF-8") + + "&version=" + 13; + String result = openUrl(parameters); + + if (result == null) + { + System.out.println("Can't connect to minecraft.net"); + return; + } + + if (!result.contains(":")) + { + System.out.println("Login Failed: " + result); + return; + } + + String[] values = result.split(":"); + Minecraft.main(new String[] {values[2].trim(), values[3].trim()}); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + + private static String openUrl(String addr) + { + try + { + URL url = new URL(addr); + java.io.InputStream is; + is = url.openConnection().getInputStream(); + java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(is)); + String buf = ""; + String line = null; + + while ((line = reader.readLine()) != null) + { + buf += "\n" + line; + } + + reader.close(); + return buf; + } + catch (IOException e) + { + e.printStackTrace(); + } + + return null; + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/client/CustomModLoadingErrorDisplayException.java b/src/minecraft/cpw/mods/fml/client/CustomModLoadingErrorDisplayException.java new file mode 100644 index 0000000..a2ad27f --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/CustomModLoadingErrorDisplayException.java @@ -0,0 +1,43 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiErrorScreen; +import cpw.mods.fml.common.IFMLHandledException; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * If a mod throws this exception during loading, it will be called back to render + * the error screen through the methods below. This error will not be cleared, and will + * not allow the game to carry on, but might be useful if your mod wishes to report + * a fatal configuration error in a pretty way. + * + * Throw this through a proxy. It won't work on the dedicated server environment. + * @author cpw + * + */ +@SideOnly(Side.CLIENT) +public abstract class CustomModLoadingErrorDisplayException extends RuntimeException implements IFMLHandledException +{ + /** + * Called after the GUI is inited by the parent code. You can do extra stuff here, maybe? + * + * @param errorScreen The error screen we're painting + * @param fontRenderer A font renderer for you + */ + public abstract void initGui(GuiErrorScreen errorScreen, FontRenderer fontRenderer); + + /** + * Draw your error to the screen. + * + *
Warning: Minecraft is in a deep error state. All it can do is stop. + * Do not try and do anything involving complex user interaction here. + * + * @param errorScreen The error screen to draw to + * @param fontRenderer A font renderer for you + * @param mouseRelX Mouse X + * @param mouseRelY Mouse Y + * @param tickTime tick time + */ + public abstract void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseRelX, int mouseRelY, float tickTime); +} diff --git a/src/minecraft/cpw/mods/fml/client/FMLClientHandler.java b/src/minecraft/cpw/mods/fml/client/FMLClientHandler.java new file mode 100644 index 0000000..535b760 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/FMLClientHandler.java @@ -0,0 +1,533 @@ +/* + * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +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.Level; +import java.util.logging.Logger; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.GuiConnecting; +import net.minecraft.client.multiplayer.NetClientHandler; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.crash.CrashReport; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.INetworkManager; +import net.minecraft.network.packet.NetHandler; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.Packet131MapData; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; + +import cpw.mods.fml.client.modloader.ModLoaderClientHelper; +import cpw.mods.fml.client.registry.KeyBindingRegistry; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.DummyModContainer; +import cpw.mods.fml.common.DuplicateModsFoundException; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.IFMLSidedHandler; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.common.MetadataCollection; +import cpw.mods.fml.common.MissingModsException; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.ModMetadata; +import cpw.mods.fml.common.ObfuscationReflectionHelper; +import cpw.mods.fml.common.WrongMinecraftVersionException; +import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; +import cpw.mods.fml.common.network.EntitySpawnPacket; +import cpw.mods.fml.common.network.ModMissingPacket; +import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.common.registry.GameData; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; +import cpw.mods.fml.common.registry.IThrowableEntity; +import cpw.mods.fml.common.registry.ItemData; +import cpw.mods.fml.common.registry.LanguageRegistry; +import cpw.mods.fml.relauncher.Side; + + +/** + * Handles primary communication from hooked code into the system + * + * The FML entry point is {@link #beginMinecraftLoading(Minecraft)} called from + * {@link Minecraft} + * + * Obfuscated code should focus on this class and other members of the "server" + * (or "client") code + * + * The actual mod loading is handled at arms length by {@link Loader} + * + * It is expected that a similar class will exist for each target environment: + * Bukkit and Client side. + * + * It should not be directly modified. + * + * @author cpw + * + */ +public class FMLClientHandler implements IFMLSidedHandler +{ + /** + * The singleton + */ + private static final FMLClientHandler INSTANCE = new FMLClientHandler(); + + /** + * A reference to the server itself + */ + private Minecraft client; + + private DummyModContainer optifineContainer; + + private boolean guiLoaded; + + private boolean serverIsRunning; + + private MissingModsException modsMissing; + + private boolean loading; + + private WrongMinecraftVersionException wrongMC; + + private CustomModLoadingErrorDisplayException customError; + + private DuplicateModsFoundException dupesFound; + + private boolean serverShouldBeKilledQuietly; + + /** + * Called to start the whole game off + * + * @param minecraft The minecraft instance being launched + */ + public void beginMinecraftLoading(Minecraft minecraft) + { + if (minecraft.isDemo()) + { + FMLLog.severe("DEMO MODE DETECTED, FML will not work. Finishing now."); + haltGame("FML will not run in demo mode", new RuntimeException()); + return; + } + + loading = true; + client = minecraft; + ObfuscationReflectionHelper.detectObfuscation(World.class); + TextureFXManager.instance().setClient(client); + FMLCommonHandler.instance().beginLoading(this); + new ModLoaderClientHelper(client); + try + { + Class optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader()); + String optifineVersion = (String) optifineConfig.getField("VERSION").get(null); + Map dummyOptifineMeta = ImmutableMap.builder().put("name", "Optifine").put("version", optifineVersion).build(); + ModMetadata optifineMetadata = MetadataCollection.from(getClass().getResourceAsStream("optifinemod.info"),"optifine").getMetadataForId("optifine", dummyOptifineMeta); + optifineContainer = new DummyModContainer(optifineMetadata); + FMLLog.info("Forge Mod Loader has detected optifine %s, enabling compatibility features",optifineContainer.getVersion()); + } + catch (Exception e) + { + optifineContainer = null; + } + try + { + Loader.instance().loadMods(); + } + catch (WrongMinecraftVersionException wrong) + { + wrongMC = wrong; + } + catch (DuplicateModsFoundException dupes) + { + dupesFound = dupes; + } + catch (MissingModsException missing) + { + modsMissing = missing; + } + catch (CustomModLoadingErrorDisplayException custom) + { + FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt"); + customError = custom; + } + catch (LoaderException le) + { + haltGame("There was a severe problem during mod loading that has caused the game to fail", le); + return; + } + } + + @Override + public void haltGame(String message, Throwable t) + { + client.displayCrashReport(new CrashReport(message, t)); + throw Throwables.propagate(t); + } + /** + * Called a bit later on during initialization to finish loading mods + * Also initializes key bindings + * + */ + @SuppressWarnings("deprecation") + public void finishMinecraftLoading() + { + if (modsMissing != null || wrongMC != null || customError!=null || dupesFound!=null) + { + return; + } + try + { + Loader.instance().initializeMods(); + } + catch (CustomModLoadingErrorDisplayException custom) + { + FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt"); + customError = custom; + return; + } + catch (LoaderException le) + { + haltGame("There was a severe problem during mod loading that has caused the game to fail", le); + return; + } + LanguageRegistry.reloadLanguageTable(); + RenderingRegistry.instance().loadEntityRenderers((Map, Render>)RenderManager.instance.entityRenderMap); + + loading = false; + KeyBindingRegistry.instance().uploadKeyBindingsToGame(client.gameSettings); + } + + public void onInitializationComplete() + { + if (wrongMC != null) + { + client.displayGuiScreen(new GuiWrongMinecraft(wrongMC)); + } + else if (modsMissing != null) + { + client.displayGuiScreen(new GuiModsMissing(modsMissing)); + } + else if (dupesFound != null) + { + client.displayGuiScreen(new GuiDupesFound(dupesFound)); + } + else if (customError != null) + { + client.displayGuiScreen(new GuiCustomModLoadingErrorScreen(customError)); + } + else + { + TextureFXManager.instance().loadTextures(client.texturePackList.getSelectedTexturePack()); + } + } + /** + * Get the server instance + */ + public Minecraft getClient() + { + return client; + } + + /** + * Get a handle to the client's logger instance + * The client actually doesn't have one- so we return null + */ + public Logger getMinecraftLogger() + { + return null; + } + + /** + * @return the instance + */ + public static FMLClientHandler instance() + { + return INSTANCE; + } + + /** + * @param player + * @param gui + */ + public void displayGuiScreen(EntityPlayer player, GuiScreen gui) + { + if (client.thePlayer==player && gui != null) { + client.displayGuiScreen(gui); + } + } + + /** + * @param mods + */ + public void addSpecialModEntries(ArrayList mods) + { + if (optifineContainer!=null) { + mods.add(optifineContainer); + } + } + + @Override + public List getAdditionalBrandingInformation() + { + if (optifineContainer!=null) + { + return Arrays.asList(String.format("Optifine %s",optifineContainer.getVersion())); + } else { + return ImmutableList.of(); + } + } + + @Override + public Side getSide() + { + return Side.CLIENT; + } + + public boolean hasOptifine() + { + return optifineContainer!=null; + } + + @Override + public void showGuiScreen(Object clientGuiElement) + { + GuiScreen gui = (GuiScreen) clientGuiElement; + client.displayGuiScreen(gui); + } + + @Override + public Entity spawnEntityIntoClientWorld(EntityRegistration er, EntitySpawnPacket packet) + { + WorldClient wc = client.theWorld; + + Class cls = er.getEntityClass(); + + try + { + Entity entity; + if (er.hasCustomSpawning()) + { + entity = er.doCustomSpawning(packet); + } + else + { + entity = (Entity)(cls.getConstructor(World.class).newInstance(wc)); + entity.entityId = packet.entityId; + entity.setLocationAndAngles(packet.scaledX, packet.scaledY, packet.scaledZ, packet.scaledYaw, packet.scaledPitch); + if (entity instanceof EntityLiving) + { + ((EntityLiving)entity).rotationYawHead = packet.scaledHeadYaw; + } + + } + + entity.serverPosX = packet.rawX; + entity.serverPosY = packet.rawY; + entity.serverPosZ = packet.rawZ; + + if (entity instanceof IThrowableEntity) + { + Entity thrower = client.thePlayer.entityId == packet.throwerId ? client.thePlayer : wc.getEntityByID(packet.throwerId); + ((IThrowableEntity)entity).setThrower(thrower); + } + + + Entity parts[] = entity.getParts(); + if (parts != null) + { + int i = packet.entityId - entity.entityId; + for (int j = 0; j < parts.length; j++) + { + parts[j].entityId += i; + } + } + + + if (packet.metadata != null) + { + entity.getDataWatcher().updateWatchedObjectsFromList((List)packet.metadata); + } + + if (packet.throwerId > 0) + { + entity.setVelocity(packet.speedScaledX, packet.speedScaledY, packet.speedScaledZ); + } + + if (entity instanceof IEntityAdditionalSpawnData) + { + ((IEntityAdditionalSpawnData)entity).readSpawnData(packet.dataStream); + } + + wc.addEntityToWorld(packet.entityId, entity); + return entity; + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "A severe problem occurred during the spawning of an entity"); + throw Throwables.propagate(e); + } + } + + @Override + public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket packet) + { + Entity ent = client.theWorld.getEntityByID(packet.entityId); + if (ent != null) + { + ent.serverPosX = packet.serverX; + ent.serverPosY = packet.serverY; + ent.serverPosZ = packet.serverZ; + } + else + { + FMLLog.fine("Attempted to adjust the position of entity %d which is not present on the client", packet.entityId); + } + } + + @Override + public void beginServerLoading(MinecraftServer server) + { + serverShouldBeKilledQuietly = false; + // NOOP + } + + @Override + public void finishServerLoading() + { + // NOOP + } + + @Override + public MinecraftServer getServer() + { + return client.getIntegratedServer(); + } + + @Override + public void sendPacket(Packet packet) + { + if(client.thePlayer != null) + { + client.thePlayer.sendQueue.addToSendQueue(packet); + } + } + + @Override + public void displayMissingMods(ModMissingPacket modMissingPacket) + { + client.displayGuiScreen(new GuiModsMissingForServer(modMissingPacket)); + } + + /** + * If the client is in the midst of loading, we disable saving so that custom settings aren't wiped out + */ + public boolean isLoading() + { + return loading; + } + + @Override + public void handleTinyPacket(NetHandler handler, Packet131MapData mapData) + { + ((NetClientHandler)handler).fmlPacket131Callback(mapData); + } + + @Override + public void setClientCompatibilityLevel(byte compatibilityLevel) + { + NetClientHandler.setConnectionCompatibilityLevel(compatibilityLevel); + } + + @Override + public byte getClientCompatibilityLevel() + { + return NetClientHandler.getConnectionCompatibilityLevel(); + } + + public void warnIDMismatch(MapDifference idDifferences, boolean mayContinue) + { + GuiIdMismatchScreen mismatch = new GuiIdMismatchScreen(idDifferences, mayContinue); + client.displayGuiScreen(mismatch); + } + + public void callbackIdDifferenceResponse(boolean response) + { + if (response) + { + serverShouldBeKilledQuietly = false; + GameData.releaseGate(true); + client.continueWorldLoading(); + } + else + { + serverShouldBeKilledQuietly = true; + GameData.releaseGate(false); + // Reset and clear the client state + client.loadWorld((WorldClient)null); + client.displayGuiScreen(null); + } + } + + @Override + public boolean shouldServerShouldBeKilledQuietly() + { + return serverShouldBeKilledQuietly; + } + + @Override + public void disconnectIDMismatch(MapDifference s, NetHandler toKill, INetworkManager mgr) + { + boolean criticalMismatch = !s.entriesOnlyOnLeft().isEmpty(); + for (Entry> mismatch : s.entriesDiffering().entrySet()) + { + ValueDifference vd = mismatch.getValue(); + if (!vd.leftValue().mayDifferByOrdinal(vd.rightValue())) + { + criticalMismatch = true; + } + } + + if (!criticalMismatch) + { + // We'll carry on with this connection, and just log a message instead + return; + } + // Nuke the connection + ((NetClientHandler)toKill).disconnect(); + // Stop GuiConnecting + GuiConnecting.forceTermination((GuiConnecting)client.currentScreen); + // pulse the network manager queue to clear cruft + mgr.processReadPackets(); + // Nuke the world client + client.loadWorld((WorldClient)null); + // Show error screen + warnIDMismatch(s, false); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/FMLTextureFX.java b/src/minecraft/cpw/mods/fml/client/FMLTextureFX.java new file mode 100644 index 0000000..d3182dc --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/FMLTextureFX.java @@ -0,0 +1,70 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import java.awt.Dimension; +import java.util.List; +import java.util.logging.Logger; + +import net.minecraft.client.renderer.RenderEngine; +import net.minecraft.client.renderer.texturefx.TextureFX; +import net.minecraft.client.texturepacks.ITexturePack; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; + +public class FMLTextureFX extends TextureFX implements ITextureFX +{ + public int tileSizeBase = 16; + public int tileSizeSquare = 256; + public int tileSizeMask = 15; + public int tileSizeSquareMask = 255; + public boolean errored = false; + protected Logger log = FMLLog.getLogger(); + + public FMLTextureFX(int icon) + { + super(icon); + } + + @Override public void setErrored(boolean err){ errored = err; } + @Override public boolean getErrored(){ return errored; } + @Override + public void onTexturePackChanged(RenderEngine engine, ITexturePack texturepack, Dimension dimensions) + { + onTextureDimensionsUpdate(dimensions.width, dimensions.height); + } + @Override + public void onTextureDimensionsUpdate(int width, int height) + { + tileSizeBase = width >> 4; + tileSizeSquare = tileSizeBase * tileSizeBase; + tileSizeMask = tileSizeBase - 1; + tileSizeSquareMask = tileSizeSquare - 1; + setErrored(false); + setup(); + } + + protected void setup() + { + imageData = new byte[tileSizeSquare << 2]; + } + + public boolean unregister(RenderEngine engine, List effects) + { + effects.remove(this); + return true; + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiCustomModLoadingErrorScreen.java b/src/minecraft/cpw/mods/fml/client/GuiCustomModLoadingErrorScreen.java new file mode 100644 index 0000000..cdfd51b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiCustomModLoadingErrorScreen.java @@ -0,0 +1,34 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiErrorScreen; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.WrongMinecraftVersionException; + +public class GuiCustomModLoadingErrorScreen extends GuiErrorScreen +{ + private CustomModLoadingErrorDisplayException customException; + public GuiCustomModLoadingErrorScreen(CustomModLoadingErrorDisplayException customException) + { + this.customException = customException; + } + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + super.initGui(); + this.customException.initGui(this, fontRenderer); + } + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + this.customException.drawScreen(this, fontRenderer, par1, par2, par3); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiDupesFound.java b/src/minecraft/cpw/mods/fml/client/GuiDupesFound.java new file mode 100644 index 0000000..67f5d6a --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiDupesFound.java @@ -0,0 +1,53 @@ +package cpw.mods.fml.client; + +import java.io.File; +import java.util.Map.Entry; + +import net.minecraft.client.gui.GuiErrorScreen; + +import cpw.mods.fml.common.DuplicateModsFoundException; +import cpw.mods.fml.common.MissingModsException; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class GuiDupesFound extends GuiErrorScreen +{ + + private DuplicateModsFoundException dupes; + + public GuiDupesFound(DuplicateModsFoundException dupes) + { + this.dupes = dupes; + } + + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + super.initGui(); + } + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + int offset = Math.max(85 - dupes.dupes.size() * 10, 10); + this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF); + offset+=10; + this.drawCenteredString(this.fontRenderer, "You have mod sources that are duplicate within your system", this.width / 2, offset, 0xFFFFFF); + offset+=10; + this.drawCenteredString(this.fontRenderer, "Mod Id : File name", this.width / 2, offset, 0xFFFFFF); + offset+=5; + for (Entry mc : dupes.dupes.entries()) + { + offset+=10; + this.drawCenteredString(this.fontRenderer, String.format("%s : %s", mc.getKey().getModId(), mc.getValue().getName()), this.width / 2, offset, 0xEEEEEE); + } + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiIdMismatchScreen.java b/src/minecraft/cpw/mods/fml/client/GuiIdMismatchScreen.java new file mode 100644 index 0000000..cf23567 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiIdMismatchScreen.java @@ -0,0 +1,92 @@ +package cpw.mods.fml.client; + +import java.util.List; +import java.util.Map.Entry; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiYesNo; +import net.minecraft.util.StringTranslate; + +import com.google.common.collect.Lists; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; + +import cpw.mods.fml.common.registry.ItemData; +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class GuiIdMismatchScreen extends GuiYesNo { + private List missingIds = Lists.newArrayList(); + private List mismatchedIds = Lists.newArrayList(); + private boolean allowContinue; + + public GuiIdMismatchScreen(MapDifference idDifferences, boolean allowContinue) + { + super(null,"ID mismatch", "Should I continue?", 1); + parentScreen = this; + for (Entry entry : idDifferences.entriesOnlyOnLeft().entrySet()) + { + missingIds.add(String.format("ID %d (ModID: %s, type %s) is missing", entry.getValue().getItemId(), entry.getValue().getModId(), entry.getValue().getItemType())); + } + for (Entry> entry : idDifferences.entriesDiffering().entrySet()) + { + ItemData world = entry.getValue().leftValue(); + ItemData game = entry.getValue().rightValue(); + mismatchedIds.add(String.format("ID %d is mismatched. World: (ModID: %s, type %s, ordinal %d) Game (ModID: %s, type %s, ordinal %d)", world.getItemId(), world.getModId(), world.getItemType(), world.getOrdinal(), game.getModId(), game.getItemType(), game.getOrdinal())); + } + this.allowContinue = allowContinue; + } + + @Override + public void confirmClicked(boolean choice, int par2) + { + FMLClientHandler.instance().callbackIdDifferenceResponse(choice); + } + + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + if (!allowContinue && controlList.size() == 2) + { + controlList.remove(0); + } + int offset = Math.max(85 - missingIds.size() * 10 + mismatchedIds.size() * 30, 10); + this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found world ID mismatches", this.width / 2, offset, 0xFFFFFF); + offset += 10; + for (String s: missingIds) { + this.drawCenteredString(this.fontRenderer, s, this.width / 2, offset, 0xEEEEEE); + offset += 10; + } + for (String s: mismatchedIds) { + this.drawCenteredString(this.fontRenderer, s, this.width / 2, offset, 0xEEEEEE); + offset += 10; + } + offset += 10; + if (allowContinue) + { + this.drawCenteredString(this.fontRenderer, "Do you wish to continue loading?", this.width / 2, offset, 0xFFFFFF); + offset += 10; + } + else + { + this.drawCenteredString(this.fontRenderer, "You cannot connect to this server", this.width / 2, offset, 0xFFFFFF); + offset += 10; + } + // super.super. Grrr + for (int var4 = 0; var4 < this.controlList.size(); ++var4) + { + GuiButton var5 = (GuiButton)this.controlList.get(var4); + var5.yPosition = Math.min(offset + 10, this.height - 20); + if (!allowContinue) + { + var5.xPosition = this.width / 2 - 75; + var5.displayString = StringTranslate.getInstance().translateKey("gui.done"); + } + var5.drawButton(this.mc, par1, par2); + } + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiModList.java b/src/minecraft/cpw/mods/fml/client/GuiModList.java new file mode 100644 index 0000000..aacaa6e --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiModList.java @@ -0,0 +1,198 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import java.awt.Dimension; +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiSmallButton; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.StringTranslate; + +import org.lwjgl.opengl.GL11; + +import com.google.common.base.Strings; + +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; + +/** + * @author cpw + * + */ +public class GuiModList extends GuiScreen +{ + private GuiScreen mainMenu; + private GuiSlotModList modList; + private int selected = -1; + private ModContainer selectedMod; + private int listWidth; + private ArrayList mods; + + /** + * @param mainMenu + */ + public GuiModList(GuiScreen mainMenu) + { + this.mainMenu=mainMenu; + this.mods=new ArrayList(); + FMLClientHandler.instance().addSpecialModEntries(mods); + for (ModContainer mod : Loader.instance().getModList()) { + if (mod.getMetadata()!=null && mod.getMetadata().parentMod==null && !Strings.isNullOrEmpty(mod.getMetadata().parent)) { + String parentMod = mod.getMetadata().parent; + ModContainer parentContainer = Loader.instance().getIndexedModList().get(parentMod); + if (parentContainer != null) + { + mod.getMetadata().parentMod = parentContainer; + parentContainer.getMetadata().childMods.add(mod); + continue; + } + } + else if (mod.getMetadata()!=null && mod.getMetadata().parentMod!=null) + { + continue; + } + mods.add(mod); + } + } + + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + for (ModContainer mod : mods) { + listWidth=Math.max(listWidth,getFontRenderer().getStringWidth(mod.getName()) + 10); + listWidth=Math.max(listWidth,getFontRenderer().getStringWidth(mod.getVersion()) + 10); + } + listWidth=Math.min(listWidth, 150); + StringTranslate translations = StringTranslate.getInstance(); + this.controlList.add(new GuiSmallButton(6, this.width / 2 - 75, this.height - 38, translations.translateKey("gui.done"))); + this.modList=new GuiSlotModList(this, mods, listWidth); + this.modList.registerScrollButtons(this.controlList, 7, 8); + } + + @Override + + /** + * Fired when a control is clicked. This is the equivalent of ActionListener.actionPerformed(ActionEvent e). + */ + protected void actionPerformed(GuiButton button) { + if (button.enabled) + { + switch (button.id) + { + case 6: + this.mc.displayGuiScreen(this.mainMenu); + return; + } + } + super.actionPerformed(button); + } + + public int drawLine(String line, int offset, int shifty) + { + this.fontRenderer.drawString(line, offset, shifty, 0xd7edea); + return shifty + 10; + } + + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int p_571_1_, int p_571_2_, float p_571_3_) + { + this.modList.drawScreen(p_571_1_, p_571_2_, p_571_3_); + this.drawCenteredString(this.fontRenderer, "Mod List", this.width / 2, 16, 0xFFFFFF); + int offset = this.listWidth + 20; + if (selectedMod != null) { + GL11.glEnable(GL11.GL_BLEND); + if (!selectedMod.getMetadata().autogenerated) { + int shifty = 35; + if (!selectedMod.getMetadata().logoFile.isEmpty()) + { + int texture = this.mc.renderEngine.getTexture(selectedMod.getMetadata().logoFile); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + this.mc.renderEngine.bindTexture(texture); + Dimension dim = TextureFXManager.instance().getTextureDimensions(texture); + int top = 32; + Tessellator tess = Tessellator.instance; + tess.startDrawingQuads(); + tess.addVertexWithUV(offset, top + dim.height, zLevel, 0, 1); + tess.addVertexWithUV(offset + dim.width, top + dim.height, zLevel, 1, 1); + tess.addVertexWithUV(offset + dim.width, top, zLevel, 1, 0); + tess.addVertexWithUV(offset, top, zLevel, 0, 0); + tess.draw(); + + shifty += 65; + } + this.fontRenderer.drawStringWithShadow(selectedMod.getMetadata().name, offset, shifty, 0xFFFFFF); + shifty += 12; + + shifty = drawLine(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion()), offset, shifty); + shifty = drawLine(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod)), offset, shifty); + if (!selectedMod.getMetadata().credits.isEmpty()) { + shifty = drawLine(String.format("Credits: %s", selectedMod.getMetadata().credits), offset, shifty); + } + shifty = drawLine(String.format("Authors: %s", selectedMod.getMetadata().getAuthorList()), offset, shifty); + shifty = drawLine(String.format("URL: %s", selectedMod.getMetadata().url), offset, shifty); + shifty = drawLine(selectedMod.getMetadata().childMods.isEmpty() ? "No child mods for this mod" : String.format("Child mods: %s", selectedMod.getMetadata().getChildModList()), offset, shifty); + this.getFontRenderer().drawSplitString(selectedMod.getMetadata().description, offset, shifty + 10, this.width - offset - 20, 0xDDDDDD); + } else { + offset = ( this.listWidth + this.width ) / 2; + this.drawCenteredString(this.fontRenderer, selectedMod.getName(), offset, 35, 0xFFFFFF); + this.drawCenteredString(this.fontRenderer, String.format("Version: %s",selectedMod.getVersion()), offset, 45, 0xFFFFFF); + this.drawCenteredString(this.fontRenderer, String.format("Mod State: %s",Loader.instance().getModState(selectedMod)), offset, 55, 0xFFFFFF); + this.drawCenteredString(this.fontRenderer, "No mod information found", offset, 65, 0xDDDDDD); + this.drawCenteredString(this.fontRenderer, "Ask your mod author to provide a mod mcmod.info file", offset, 75, 0xDDDDDD); + } + GL11.glDisable(GL11.GL_BLEND); + } + super.drawScreen(p_571_1_, p_571_2_, p_571_3_); + } + + Minecraft getMinecraftInstance() { + return mc; + } + + FontRenderer getFontRenderer() { + return fontRenderer; + } + + /** + * @param var1 + */ + public void selectModIndex(int var1) + { + this.selected=var1; + if (var1>=0 && var1<=mods.size()) { + this.selectedMod=mods.get(selected); + } else { + this.selectedMod=null; + } + } + + public boolean modIndexSelected(int var1) + { + return var1==selected; + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiModsMissing.java b/src/minecraft/cpw/mods/fml/client/GuiModsMissing.java new file mode 100644 index 0000000..aa348e4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiModsMissing.java @@ -0,0 +1,47 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiErrorScreen; +import cpw.mods.fml.common.MissingModsException; +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class GuiModsMissing extends GuiErrorScreen +{ + + private MissingModsException modsMissing; + + public GuiModsMissing(MissingModsException modsMissing) + { + this.modsMissing = modsMissing; + } + + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + super.initGui(); + } + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + int offset = Math.max(85 - modsMissing.missingMods.size() * 10, 10); + this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF); + offset+=10; + this.drawCenteredString(this.fontRenderer, "The mods and versions listed below could not be found", this.width / 2, offset, 0xFFFFFF); + offset+=5; + for (ArtifactVersion v : modsMissing.missingMods) + { + offset+=10; + this.drawCenteredString(this.fontRenderer, String.format("%s : %s", v.getLabel(), v.getRangeString()), this.width / 2, offset, 0xEEEEEE); + } + offset+=20; + this.drawCenteredString(this.fontRenderer, "The file 'ForgeModLoader-client-0.log' contains more information", this.width / 2, offset, 0xFFFFFF); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiModsMissingForServer.java b/src/minecraft/cpw/mods/fml/client/GuiModsMissingForServer.java new file mode 100644 index 0000000..b340f8a --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiModsMissingForServer.java @@ -0,0 +1,64 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiSmallButton; +import net.minecraft.util.StringTranslate; +import cpw.mods.fml.common.network.ModMissingPacket; +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class GuiModsMissingForServer extends GuiScreen +{ + private ModMissingPacket modsMissing; + + public GuiModsMissingForServer(ModMissingPacket modsMissing) + { + this.modsMissing = modsMissing; + } + + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + StringTranslate translations = StringTranslate.getInstance(); + this.controlList.add(new GuiSmallButton(1, this.width / 2 - 75, this.height - 38, translations.translateKey("gui.done"))); + } + + @Override + + /** + * Fired when a control is clicked. This is the equivalent of ActionListener.actionPerformed(ActionEvent e). + */ + protected void actionPerformed(GuiButton par1GuiButton) + { + if (par1GuiButton.enabled && par1GuiButton.id == 1) + { + FMLClientHandler.instance().getClient().displayGuiScreen(null); + } + } + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + int offset = Math.max(85 - modsMissing.getModList().size() * 10, 10); + this.drawCenteredString(this.fontRenderer, "Forge Mod Loader could not connect to this server", this.width / 2, offset, 0xFFFFFF); + offset += 10; + this.drawCenteredString(this.fontRenderer, "The mods and versions listed below could not be found", this.width / 2, offset, 0xFFFFFF); + offset += 10; + this.drawCenteredString(this.fontRenderer, "They are required to play on this server", this.width / 2, offset, 0xFFFFFF); + offset += 5; + for (ArtifactVersion v : modsMissing.getModList()) + { + offset += 10; + this.drawCenteredString(this.fontRenderer, String.format("%s : %s", v.getLabel(), v.getRangeString()), this.width / 2, offset, 0xEEEEEE); + } + super.drawScreen(par1, par2, par3); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiScrollingList.java b/src/minecraft/cpw/mods/fml/client/GuiScrollingList.java new file mode 100644 index 0000000..bb2d611 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiScrollingList.java @@ -0,0 +1,400 @@ +package cpw.mods.fml.client; + +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.Tessellator; + +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public abstract class GuiScrollingList +{ + private final Minecraft client; + protected final int listWidth; + protected final int listHeight; + protected final int top; + protected final int bottom; + private final int right; + protected final int left; + protected final int slotHeight; + private int scrollUpActionId; + private int scrollDownActionId; + protected int mouseX; + protected int mouseY; + private float initialMouseClickY = -2.0F; + private float scrollFactor; + private float scrollDistance; + private int selectedIndex = -1; + private long lastClickTime = 0L; + private boolean field_25123_p = true; + private boolean field_27262_q; + private int field_27261_r; + + public GuiScrollingList(Minecraft client, int width, int height, int top, int bottom, int left, int entryHeight) + { + this.client = client; + this.listWidth = width; + this.listHeight = height; + this.top = top; + this.bottom = bottom; + this.slotHeight = entryHeight; + this.left = left; + this.right = width + this.left; + } + + public void func_27258_a(boolean p_27258_1_) + { + this.field_25123_p = p_27258_1_; + } + + protected void func_27259_a(boolean p_27259_1_, int p_27259_2_) + { + this.field_27262_q = p_27259_1_; + this.field_27261_r = p_27259_2_; + + if (!p_27259_1_) + { + this.field_27261_r = 0; + } + } + + protected abstract int getSize(); + + protected abstract void elementClicked(int index, boolean doubleClick); + + protected abstract boolean isSelected(int index); + + protected int getContentHeight() + { + return this.getSize() * this.slotHeight + this.field_27261_r; + } + + protected abstract void drawBackground(); + + protected abstract void drawSlot(int var1, int var2, int var3, int var4, Tessellator var5); + + protected void func_27260_a(int p_27260_1_, int p_27260_2_, Tessellator p_27260_3_) {} + + protected void func_27255_a(int p_27255_1_, int p_27255_2_) {} + + protected void func_27257_b(int p_27257_1_, int p_27257_2_) {} + + public int func_27256_c(int p_27256_1_, int p_27256_2_) + + { + int var3 = this.left + 1; + int var4 = this.left + this.listWidth - 7; + int var5 = p_27256_2_ - this.top - this.field_27261_r + (int)this.scrollDistance - 4; + int var6 = var5 / this.slotHeight; + return p_27256_1_ >= var3 && p_27256_1_ <= var4 && var6 >= 0 && var5 >= 0 && var6 < this.getSize() ? var6 : -1; + } + + public void registerScrollButtons(List p_22240_1_, int p_22240_2_, int p_22240_3_) + { + this.scrollUpActionId = p_22240_2_; + this.scrollDownActionId = p_22240_3_; + } + + private void applyScrollLimits() + { + int var1 = this.getContentHeight() - (this.bottom - this.top - 4); + + if (var1 < 0) + { + var1 /= 2; + } + + if (this.scrollDistance < 0.0F) + { + this.scrollDistance = 0.0F; + } + + if (this.scrollDistance > (float)var1) + { + this.scrollDistance = (float)var1; + } + } + + public void actionPerformed(GuiButton button) + { + if (button.enabled) + { + if (button.id == this.scrollUpActionId) + { + this.scrollDistance -= (float)(this.slotHeight * 2 / 3); + this.initialMouseClickY = -2.0F; + this.applyScrollLimits(); + } + else if (button.id == this.scrollDownActionId) + { + this.scrollDistance += (float)(this.slotHeight * 2 / 3); + this.initialMouseClickY = -2.0F; + this.applyScrollLimits(); + } + } + } + + public void drawScreen(int mouseX, int mouseY, float p_22243_3_) + { + this.mouseX = mouseX; + this.mouseY = mouseY; + this.drawBackground(); + int listLength = this.getSize(); + int scrollBarXStart = this.left + this.listWidth - 6; + int scrollBarXEnd = scrollBarXStart + 6; + int boxLeft = this.left; + int boxRight = scrollBarXStart-1; + int var10; + int var11; + int var13; + int var19; + + if (Mouse.isButtonDown(0)) + { + if (this.initialMouseClickY == -1.0F) + { + boolean var7 = true; + + if (mouseY >= this.top && mouseY <= this.bottom) + { + var10 = mouseY - this.top - this.field_27261_r + (int)this.scrollDistance - 4; + var11 = var10 / this.slotHeight; + + if (mouseX >= boxLeft && mouseX <= boxRight && var11 >= 0 && var10 >= 0 && var11 < listLength) + { + boolean var12 = var11 == this.selectedIndex && System.currentTimeMillis() - this.lastClickTime < 250L; + this.elementClicked(var11, var12); + this.selectedIndex = var11; + this.lastClickTime = System.currentTimeMillis(); + } + else if (mouseX >= boxLeft && mouseX <= boxRight && var10 < 0) + { + this.func_27255_a(mouseX - boxLeft, mouseY - this.top + (int)this.scrollDistance - 4); + var7 = false; + } + + if (mouseX >= scrollBarXStart && mouseX <= scrollBarXEnd) + { + this.scrollFactor = -1.0F; + var19 = this.getContentHeight() - (this.bottom - this.top - 4); + + if (var19 < 1) + { + var19 = 1; + } + + var13 = (int)((float)((this.bottom - this.top) * (this.bottom - this.top)) / (float)this.getContentHeight()); + + if (var13 < 32) + { + var13 = 32; + } + + if (var13 > this.bottom - this.top - 8) + { + var13 = this.bottom - this.top - 8; + } + + this.scrollFactor /= (float)(this.bottom - this.top - var13) / (float)var19; + } + else + { + this.scrollFactor = 1.0F; + } + + if (var7) + { + this.initialMouseClickY = (float)mouseY; + } + else + { + this.initialMouseClickY = -2.0F; + } + } + else + { + this.initialMouseClickY = -2.0F; + } + } + else if (this.initialMouseClickY >= 0.0F) + { + this.scrollDistance -= ((float)mouseY - this.initialMouseClickY) * this.scrollFactor; + this.initialMouseClickY = (float)mouseY; + } + } + else + { + while (Mouse.next()) + { + int var16 = Mouse.getEventDWheel(); + + if (var16 != 0) + { + if (var16 > 0) + { + var16 = -1; + } + else if (var16 < 0) + { + var16 = 1; + } + + this.scrollDistance += (float)(var16 * this.slotHeight / 2); + } + } + + this.initialMouseClickY = -1.0F; + } + + this.applyScrollLimits(); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_FOG); + Tessellator var18 = Tessellator.instance; + GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.client.renderEngine.getTexture("/gui/background.png")); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + float var17 = 32.0F; + var18.startDrawingQuads(); + var18.setColorOpaque_I(2105376); + var18.addVertexWithUV((double)this.left, (double)this.bottom, 0.0D, (double)((float)this.left / var17), (double)((float)(this.bottom + (int)this.scrollDistance) / var17)); + var18.addVertexWithUV((double)this.right, (double)this.bottom, 0.0D, (double)((float)this.right / var17), (double)((float)(this.bottom + (int)this.scrollDistance) / var17)); + var18.addVertexWithUV((double)this.right, (double)this.top, 0.0D, (double)((float)this.right / var17), (double)((float)(this.top + (int)this.scrollDistance) / var17)); + var18.addVertexWithUV((double)this.left, (double)this.top, 0.0D, (double)((float)this.left / var17), (double)((float)(this.top + (int)this.scrollDistance) / var17)); + var18.draw(); +// boxRight = this.listWidth / 2 - 92 - 16; + var10 = this.top + 4 - (int)this.scrollDistance; + + if (this.field_27262_q) + { + this.func_27260_a(boxRight, var10, var18); + } + + int var14; + + for (var11 = 0; var11 < listLength; ++var11) + { + var19 = var10 + var11 * this.slotHeight + this.field_27261_r; + var13 = this.slotHeight - 4; + + if (var19 <= this.bottom && var19 + var13 >= this.top) + { + if (this.field_25123_p && this.isSelected(var11)) + { + var14 = boxLeft; + int var15 = boxRight; + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + GL11.glDisable(GL11.GL_TEXTURE_2D); + var18.startDrawingQuads(); + var18.setColorOpaque_I(8421504); + var18.addVertexWithUV((double)var14, (double)(var19 + var13 + 2), 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)var15, (double)(var19 + var13 + 2), 0.0D, 1.0D, 1.0D); + var18.addVertexWithUV((double)var15, (double)(var19 - 2), 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)var14, (double)(var19 - 2), 0.0D, 0.0D, 0.0D); + var18.setColorOpaque_I(0); + var18.addVertexWithUV((double)(var14 + 1), (double)(var19 + var13 + 1), 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)(var15 - 1), (double)(var19 + var13 + 1), 0.0D, 1.0D, 1.0D); + var18.addVertexWithUV((double)(var15 - 1), (double)(var19 - 1), 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)(var14 + 1), (double)(var19 - 1), 0.0D, 0.0D, 0.0D); + var18.draw(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + } + + this.drawSlot(var11, boxRight, var19, var13, var18); + } + } + + GL11.glDisable(GL11.GL_DEPTH_TEST); + byte var20 = 4; + this.overlayBackground(0, this.top, 255, 255); + this.overlayBackground(this.bottom, this.listHeight, 255, 255); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDisable(GL11.GL_ALPHA_TEST); + GL11.glShadeModel(GL11.GL_SMOOTH); + GL11.glDisable(GL11.GL_TEXTURE_2D); + var18.startDrawingQuads(); + var18.setColorRGBA_I(0, 0); + var18.addVertexWithUV((double)this.left, (double)(this.top + var20), 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)this.right, (double)(this.top + var20), 0.0D, 1.0D, 1.0D); + var18.setColorRGBA_I(0, 255); + var18.addVertexWithUV((double)this.right, (double)this.top, 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)this.left, (double)this.top, 0.0D, 0.0D, 0.0D); + var18.draw(); + var18.startDrawingQuads(); + var18.setColorRGBA_I(0, 255); + var18.addVertexWithUV((double)this.left, (double)this.bottom, 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)this.right, (double)this.bottom, 0.0D, 1.0D, 1.0D); + var18.setColorRGBA_I(0, 0); + var18.addVertexWithUV((double)this.right, (double)(this.bottom - var20), 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)this.left, (double)(this.bottom - var20), 0.0D, 0.0D, 0.0D); + var18.draw(); + var19 = this.getContentHeight() - (this.bottom - this.top - 4); + + if (var19 > 0) + { + var13 = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight(); + + if (var13 < 32) + { + var13 = 32; + } + + if (var13 > this.bottom - this.top - 8) + { + var13 = this.bottom - this.top - 8; + } + + var14 = (int)this.scrollDistance * (this.bottom - this.top - var13) / var19 + this.top; + + if (var14 < this.top) + { + var14 = this.top; + } + + var18.startDrawingQuads(); + var18.setColorRGBA_I(0, 255); + var18.addVertexWithUV((double)scrollBarXStart, (double)this.bottom, 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)scrollBarXEnd, (double)this.bottom, 0.0D, 1.0D, 1.0D); + var18.addVertexWithUV((double)scrollBarXEnd, (double)this.top, 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)scrollBarXStart, (double)this.top, 0.0D, 0.0D, 0.0D); + var18.draw(); + var18.startDrawingQuads(); + var18.setColorRGBA_I(8421504, 255); + var18.addVertexWithUV((double)scrollBarXStart, (double)(var14 + var13), 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)scrollBarXEnd, (double)(var14 + var13), 0.0D, 1.0D, 1.0D); + var18.addVertexWithUV((double)scrollBarXEnd, (double)var14, 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)scrollBarXStart, (double)var14, 0.0D, 0.0D, 0.0D); + var18.draw(); + var18.startDrawingQuads(); + var18.setColorRGBA_I(12632256, 255); + var18.addVertexWithUV((double)scrollBarXStart, (double)(var14 + var13 - 1), 0.0D, 0.0D, 1.0D); + var18.addVertexWithUV((double)(scrollBarXEnd - 1), (double)(var14 + var13 - 1), 0.0D, 1.0D, 1.0D); + var18.addVertexWithUV((double)(scrollBarXEnd - 1), (double)var14, 0.0D, 1.0D, 0.0D); + var18.addVertexWithUV((double)scrollBarXStart, (double)var14, 0.0D, 0.0D, 0.0D); + var18.draw(); + } + + this.func_27257_b(mouseX, mouseY); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glShadeModel(GL11.GL_FLAT); + GL11.glEnable(GL11.GL_ALPHA_TEST); + GL11.glDisable(GL11.GL_BLEND); + } + + private void overlayBackground(int p_22239_1_, int p_22239_2_, int p_22239_3_, int p_22239_4_) + { + Tessellator var5 = Tessellator.instance; + GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.client.renderEngine.getTexture("/gui/background.png")); + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + float var6 = 32.0F; + var5.startDrawingQuads(); + var5.setColorRGBA_I(4210752, p_22239_4_); + var5.addVertexWithUV(0.0D, (double)p_22239_2_, 0.0D, 0.0D, (double)((float)p_22239_2_ / var6)); + var5.addVertexWithUV((double)this.listWidth + 30, (double)p_22239_2_, 0.0D, (double)((float)(this.listWidth + 30) / var6), (double)((float)p_22239_2_ / var6)); + var5.setColorRGBA_I(4210752, p_22239_3_); + var5.addVertexWithUV((double)this.listWidth + 30, (double)p_22239_1_, 0.0D, (double)((float)(this.listWidth + 30) / var6), (double)((float)p_22239_1_ / var6)); + var5.addVertexWithUV(0.0D, (double)p_22239_1_, 0.0D, 0.0D, (double)((float)p_22239_1_ / var6)); + var5.draw(); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiSlotModList.java b/src/minecraft/cpw/mods/fml/client/GuiSlotModList.java new file mode 100644 index 0000000..3b9dd0c --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiSlotModList.java @@ -0,0 +1,89 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import java.util.ArrayList; + +import net.minecraft.client.renderer.Tessellator; + +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.LoaderState.ModState; +import cpw.mods.fml.common.ModContainer; + +/** + * @author cpw + * + */ +public class GuiSlotModList extends GuiScrollingList +{ + private GuiModList parent; + private ArrayList mods; + + public GuiSlotModList(GuiModList parent, ArrayList mods, int listWidth) + { + super(parent.getMinecraftInstance(), listWidth, parent.height, 32, parent.height - 65 + 4, 10, 35); + this.parent=parent; + this.mods=mods; + } + + @Override + protected int getSize() + { + return mods.size(); + } + + @Override + protected void elementClicked(int var1, boolean var2) + { + this.parent.selectModIndex(var1); + } + + @Override + protected boolean isSelected(int var1) + { + return this.parent.modIndexSelected(var1); + } + + @Override + protected void drawBackground() + { + this.parent.drawDefaultBackground(); + } + + @Override + protected int getContentHeight() + { + return (this.getSize()) * 35 + 1; + } + + @Override + protected void drawSlot(int listIndex, int var2, int var3, int var4, Tessellator var5) + { + ModContainer mc=mods.get(listIndex); + if (Loader.instance().getModState(mc)==ModState.DISABLED) + { + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getName(), listWidth - 10), this.left + 3 , var3 + 2, 0xFF2222); + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getDisplayVersion(), listWidth - 10), this.left + 3 , var3 + 12, 0xFF2222); + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth("DISABLED", listWidth - 10), this.left + 3 , var3 + 22, 0xFF2222); + } + else + { + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getName(), listWidth - 10), this.left + 3 , var3 + 2, 0xFFFFFF); + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getDisplayVersion(), listWidth - 10), this.left + 3 , var3 + 12, 0xCCCCCC); + this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getMetadata() !=null ? mc.getMetadata().getChildModCountString() : "Metadata not found", listWidth - 10), this.left + 3 , var3 + 22, 0xCCCCCC); + } + } + +} diff --git a/src/minecraft/cpw/mods/fml/client/GuiWrongMinecraft.java b/src/minecraft/cpw/mods/fml/client/GuiWrongMinecraft.java new file mode 100644 index 0000000..ca55801 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/GuiWrongMinecraft.java @@ -0,0 +1,42 @@ +package cpw.mods.fml.client; + +import net.minecraft.client.gui.GuiErrorScreen; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.WrongMinecraftVersionException; +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class GuiWrongMinecraft extends GuiErrorScreen +{ + private WrongMinecraftVersionException wrongMC; + public GuiWrongMinecraft(WrongMinecraftVersionException wrongMC) + { + this.wrongMC = wrongMC; + } + @Override + + /** + * Adds the buttons (and other controls) to the screen in question. + */ + public void initGui() + { + super.initGui(); + } + @Override + + /** + * Draws the screen and all the components in it. + */ + public void drawScreen(int par1, int par2, float par3) + { + this.drawDefaultBackground(); + int offset = 75; + this.drawCenteredString(this.fontRenderer, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF); + offset+=10; + this.drawCenteredString(this.fontRenderer, String.format("The mod listed below does not want to run in Minecraft version %s", Loader.instance().getMinecraftModContainer().getVersion()), this.width / 2, offset, 0xFFFFFF); + offset+=5; + offset+=10; + this.drawCenteredString(this.fontRenderer, String.format("%s (%s) wants Minecraft %s", wrongMC.mod.getName(), wrongMC.mod.getModId(), wrongMC.mod.acceptableMinecraftVersionRange()), this.width / 2, offset, 0xEEEEEE); + offset+=20; + this.drawCenteredString(this.fontRenderer, "The file 'ForgeModLoader-client-0.log' contains more information", this.width / 2, offset, 0xFFFFFF); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/ITextureFX.java b/src/minecraft/cpw/mods/fml/client/ITextureFX.java new file mode 100644 index 0000000..0dca285 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/ITextureFX.java @@ -0,0 +1,31 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import java.awt.Dimension; + +import net.minecraft.client.renderer.RenderEngine; +import net.minecraft.client.texturepacks.ITexturePack; + +public interface ITextureFX +{ + public void onTexturePackChanged(RenderEngine engine, ITexturePack texturepack, Dimension dimensions); + + public void onTextureDimensionsUpdate(int width, int height); + + public void setErrored(boolean errored); + + public boolean getErrored(); +} diff --git a/src/minecraft/cpw/mods/fml/client/OverrideInfo.java b/src/minecraft/cpw/mods/fml/client/OverrideInfo.java new file mode 100644 index 0000000..f0d6fe2 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/OverrideInfo.java @@ -0,0 +1,44 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import net.minecraft.client.renderer.texturefx.TextureFX; + +class OverrideInfo +{ + public String texture; + public String override; + public int index; + public int imageIndex; + public TextureFX textureFX; + public boolean added; + + @Override + public boolean equals(Object obj) + { + try { + OverrideInfo inf=(OverrideInfo) obj; + return index==inf.index && imageIndex==inf.imageIndex; + } catch (Exception e) { + return false; + } + } + + @Override + public int hashCode() + { + return index+imageIndex; + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/client/SpriteHelper.java b/src/minecraft/cpw/mods/fml/client/SpriteHelper.java new file mode 100644 index 0000000..e65439e --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/SpriteHelper.java @@ -0,0 +1,138 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.logging.Level; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; + +/** + * @author cpw + * + */ +public class SpriteHelper +{ + private static HashMap spriteInfo = new HashMap(); + + private static void initMCSpriteMaps() { + BitSet slots = + SpriteHelper.toBitSet( + "0000000000000000" + + "0000000000110000" + + "0000000000100000" + + "0000000001100000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000011111" + + "0000000000000000" + + "0000000001111100" + + "0000000001111000" + + "0000000000000000"); + spriteInfo.put("/terrain.png", slots); + + slots = SpriteHelper.toBitSet( + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0000000000000000" + + "0111110000000000" + + "1111111010000000" + + "0111111110000000" + + "0111111111111001" + + "1111111111111111" + + "0000011111111111" + + "0000000000000000"); + spriteInfo.put("/gui/items.png", slots); + } + /** + * Register a sprite map for ModTextureStatic, to allow for other mods to override + * your sprite page. + * + * + */ + public static void registerSpriteMapForFile(String file, String spriteMap) { + if (spriteInfo.size() == 0) { + initMCSpriteMaps(); + } + if (spriteInfo.containsKey(file)) { + FMLCommonHandler.instance().getFMLLogger().finer(String.format("Duplicate attempt to register a sprite file %s for overriding -- ignoring",file)); + return; + } + spriteInfo.put(file, toBitSet(spriteMap)); + } + + public static int getUniqueSpriteIndex(String path) + { + if (!spriteInfo.containsKey("/terrain.png")) + { + initMCSpriteMaps(); + } + + BitSet slots = spriteInfo.get(path); + + if (slots == null) + { + Exception ex = new Exception(String.format("Invalid getUniqueSpriteIndex call for texture: %s", path)); + FMLLog.log(Level.SEVERE, ex, "A critical error has been detected with sprite overrides"); + FMLCommonHandler.instance().raiseException(ex,"Invalid request to getUniqueSpriteIndex",true); + } + + int ret = getFreeSlot(slots); + + if (ret == -1) + { + Exception ex = new Exception(String.format("No more sprite indicies left for: %s", path)); + FMLLog.log(Level.SEVERE, ex, "There are no sprite indicies left for %s", path); + FMLCommonHandler.instance().raiseException(ex,"No more sprite indicies left", true); + } + return ret; + } + + public static BitSet toBitSet(String data) + { + BitSet ret = new BitSet(data.length()); + for (int x = 0; x < data.length(); x++) + { + ret.set(x, data.charAt(x) == '1'); + } + return ret; + } + + public static int getFreeSlot(BitSet slots) + { + int next=slots.nextSetBit(0); + slots.clear(next); + return next; + } + + public static int freeSlotCount(String textureToOverride) + { + return spriteInfo.get(textureToOverride).cardinality(); + } + +} diff --git a/src/minecraft/cpw/mods/fml/client/TextureFXManager.java b/src/minecraft/cpw/mods/fml/client/TextureFXManager.java new file mode 100644 index 0000000..57c9195 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/TextureFXManager.java @@ -0,0 +1,319 @@ +package cpw.mods.fml.client; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D; + +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import javax.imageio.ImageIO; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderEngine; +import net.minecraft.client.renderer.texturefx.TextureFX; +import net.minecraft.client.texturepacks.ITexturePack; +import net.minecraft.src.ModTextureStatic; + +import org.lwjgl.opengl.GL11; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.ModContainer; + +public class TextureFXManager +{ + private static final TextureFXManager INSTANCE = new TextureFXManager(); + + private class TextureProperties + { + private int textureId; + private Dimension dim; + } + + private Map textureProperties = Maps.newHashMap(); + private Multimap overrideInfo = ArrayListMultimap.create(); + private HashSet animationSet = new HashSet(); + + private List addedTextureFX = new ArrayList(); + + private Minecraft client; + + void setClient(Minecraft client) + { + this.client = client; + } + + public boolean onUpdateTextureEffect(TextureFX effect) + { + ITextureFX ifx = (effect instanceof ITextureFX ? ((ITextureFX)effect) : null); + + if (ifx != null && ifx.getErrored()) + { + return false; + } + + String name = effect.getClass().getSimpleName(); + client.mcProfiler.startSection(name); + try + { + if (!FMLClientHandler.instance().hasOptifine()) + { + effect.onTick(); + } + } + catch (Exception e) + { + FMLLog.warning("Texture FX %s has failed to animate. Likely caused by a texture pack change that they did not respond correctly to", name); + if (ifx != null) + { + ifx.setErrored(true); + } + client.mcProfiler.endSection(); + return false; + } + client.mcProfiler.endSection(); + + if (ifx != null) + { + Dimension dim = getTextureDimensions(effect); + int target = ((dim.width >> 4) * (dim.height >> 4)) << 2; + if (effect.imageData.length != target) + { + FMLLog.warning("Detected a texture FX sizing discrepancy in %s (%d, %d)", name, effect.imageData.length, target); + ifx.setErrored(true); + return false; + } + } + return true; + } + + //Quick and dirty image scaling, no smoothing or fanciness, meant for speed as it will be called every tick. + public void scaleTextureFXData(byte[] data, ByteBuffer buf, int target, int length) + { + int sWidth = (int)Math.sqrt(data.length / 4); + int factor = target / sWidth; + byte[] tmp = new byte[4]; + + buf.clear(); + + if (factor > 1) + { + for (int y = 0; y < sWidth; y++) + { + int sRowOff = sWidth * y; + int tRowOff = target * y * factor; + for (int x = 0; x < sWidth; x++) + { + int sPos = (x + sRowOff) * 4; + tmp[0] = data[sPos + 0]; + tmp[1] = data[sPos + 1]; + tmp[2] = data[sPos + 2]; + tmp[3] = data[sPos + 3]; + + int tPosTop = (x * factor) + tRowOff; + for (int y2 = 0; y2 < factor; y2++) + { + buf.position((tPosTop + (y2 * target)) * 4); + for (int x2 = 0; x2 < factor; x2++) + { + buf.put(tmp); + } + } + } + } + } + + buf.position(0).limit(length); + } + + public void onPreRegisterEffect(TextureFX effect) + { + Dimension dim = getTextureDimensions(effect); + if (effect instanceof ITextureFX) + { + ((ITextureFX)effect).onTextureDimensionsUpdate(dim.width, dim.height); + } + } + + + public int getEffectTexture(TextureFX effect) + { + Integer id = effectTextures.get(effect); + if (id != null) + { + return id; + } + + int old = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); + effect.bindImage(client.renderEngine); + id = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); + GL11.glBindTexture(GL_TEXTURE_2D, old); + effectTextures.put(effect, id); + effect.textureId = id; + return id; + } + + public void onTexturePackChange(RenderEngine engine, ITexturePack texturepack, List effects) + { + pruneOldTextureFX(texturepack, effects); + + for (TextureFX tex : effects) + { + if (tex instanceof ITextureFX) + { + ((ITextureFX)tex).onTexturePackChanged(engine, texturepack, getTextureDimensions(tex)); + } + } + + loadTextures(texturepack); + } + + private HashMap textureDims = new HashMap(); + private IdentityHashMap effectTextures = new IdentityHashMap(); + private ITexturePack earlyTexturePack; + public void setTextureDimensions(int id, int width, int height, List effects) + { + Dimension dim = new Dimension(width, height); + textureDims.put(id, dim); + + for (TextureFX tex : effects) + { + if (getEffectTexture(tex) == id && tex instanceof ITextureFX) + { + ((ITextureFX)tex).onTextureDimensionsUpdate(width, height); + } + } + } + + public Dimension getTextureDimensions(TextureFX effect) + { + return getTextureDimensions(getEffectTexture(effect)); + } + + public Dimension getTextureDimensions(int id) + { + return textureDims.get(id); + } + + public void addAnimation(TextureFX anim) + { + OverrideInfo info=new OverrideInfo(); + info.index=anim.iconIndex; + info.imageIndex=anim.tileImage; + info.textureFX=anim; + if (animationSet.contains(info)) { + animationSet.remove(info); + } + animationSet.add(info); + } + + + public void loadTextures(ITexturePack texturePack) + { + registerTextureOverrides(client.renderEngine); + } + + + public void registerTextureOverrides(RenderEngine renderer) { + for (OverrideInfo animationOverride : animationSet) { + renderer.registerTextureFX(animationOverride.textureFX); + addedTextureFX.add(animationOverride.textureFX); + FMLCommonHandler.instance().getFMLLogger().finer(String.format("Registered texture override %d (%d) on %s (%d)", animationOverride.index, animationOverride.textureFX.iconIndex, animationOverride.textureFX.getClass().getSimpleName(), animationOverride.textureFX.tileImage)); + } + + for (String fileToOverride : overrideInfo.keySet()) { + for (OverrideInfo override : overrideInfo.get(fileToOverride)) { + try + { + BufferedImage image=loadImageFromTexturePack(renderer, override.override); + ModTextureStatic mts=new ModTextureStatic(override.index, 1, override.texture, image); + renderer.registerTextureFX(mts); + addedTextureFX.add(mts); + FMLCommonHandler.instance().getFMLLogger().finer(String.format("Registered texture override %d (%d) on %s (%d)", override.index, mts.iconIndex, override.texture, mts.tileImage)); + } + catch (IOException e) + { + FMLCommonHandler.instance().getFMLLogger().throwing("FMLClientHandler", "registerTextureOverrides", e); + } + } + } + } + + protected void registerAnimatedTexturesFor(ModContainer mod) + { + } + + public void onEarlyTexturePackLoad(ITexturePack fallback) + { + if (client==null) { + // We're far too early- let's wait + this.earlyTexturePack = fallback; + } else { + loadTextures(fallback); + } + } + + + public void pruneOldTextureFX(ITexturePack var1, List effects) + { + ListIterator li = addedTextureFX.listIterator(); + while (li.hasNext()) + { + TextureFX tex = li.next(); + if (tex instanceof FMLTextureFX) + { + if (((FMLTextureFX)tex).unregister(client.renderEngine, effects)) + { + li.remove(); + } + } + else + { + effects.remove(tex); + li.remove(); + } + } + } + public void addNewTextureOverride(String textureToOverride, String overridingTexturePath, int location) { + OverrideInfo info = new OverrideInfo(); + info.index = location; + info.override = overridingTexturePath; + info.texture = textureToOverride; + overrideInfo.put(textureToOverride, info); + FMLLog.fine("Overriding %s @ %d with %s. %d slots remaining",textureToOverride, location, overridingTexturePath, SpriteHelper.freeSlotCount(textureToOverride)); + } + + public BufferedImage loadImageFromTexturePack(RenderEngine renderEngine, String path) throws IOException + { + InputStream image=client.texturePackList.getSelectedTexturePack().getResourceAsStream(path); + if (image==null) { + throw new RuntimeException(String.format("The requested image path %s is not found",path)); + } + BufferedImage result=ImageIO.read(image); + if (result==null) + { + throw new RuntimeException(String.format("The requested image path %s appears to be corrupted",path)); + } + return result; + } + + public static TextureFXManager instance() + { + return INSTANCE; + } + +} diff --git a/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderBlockRendererHandler.java b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderBlockRendererHandler.java new file mode 100644 index 0000000..83313a4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderBlockRendererHandler.java @@ -0,0 +1,83 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client.modloader; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.src.BaseMod; +import net.minecraft.world.IBlockAccess; +import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler; + +/** + * @author cpw + * + */ +public class ModLoaderBlockRendererHandler implements ISimpleBlockRenderingHandler +{ + private int renderId; + private boolean render3dInInventory; + private BaseMod mod; + + /** + * @param mod + * + */ + public ModLoaderBlockRendererHandler(int renderId, boolean render3dInInventory, BaseMod mod) + { + this.renderId=renderId; + this.render3dInInventory=render3dInInventory; + this.mod=mod; + } + + @Override + public int getRenderId() + { + return renderId; + } + + @Override + public boolean shouldRender3DInInventory() + { + return render3dInInventory; + } + + /** + * @param world + * @param x + * @param y + * @param z + * @param block + * @param modelId + * @param renderer + */ + @Override + public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) + { + return mod.renderWorldBlock(renderer, world, x, y, z, block, modelId); + } + + /** + * @param block + * @param metadata + * @param modelID + * @param renderer + */ + @Override + public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer) + { + mod.renderInvBlock(renderer, block, metadata, modelID); + } + +} diff --git a/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderClientHelper.java b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderClientHelper.java new file mode 100644 index 0000000..52dc590 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderClientHelper.java @@ -0,0 +1,178 @@ +package cpw.mods.fml.client.modloader; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.INetworkManager; +import net.minecraft.network.packet.NetHandler; +import net.minecraft.network.packet.Packet250CustomPayload; +import net.minecraft.src.BaseMod; +import net.minecraft.client.*; +import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.client.multiplayer.NetClientHandler; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; + +import com.google.common.base.Equivalences; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.client.registry.KeyBindingRegistry; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.modloader.BaseModProxy; +import cpw.mods.fml.common.modloader.IModLoaderSidedHelper; +import cpw.mods.fml.common.modloader.ModLoaderHelper; +import cpw.mods.fml.common.modloader.ModLoaderModContainer; +import cpw.mods.fml.common.network.EntitySpawnPacket; +import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; + +public class ModLoaderClientHelper implements IModLoaderSidedHelper +{ + public static int obtainBlockModelIdFor(BaseMod mod, boolean inventoryRenderer) + { + int renderId=RenderingRegistry.getNextAvailableRenderId(); + ModLoaderBlockRendererHandler bri=new ModLoaderBlockRendererHandler(renderId, inventoryRenderer, mod); + RenderingRegistry.registerBlockHandler(bri); + return renderId; + } + + + public static void handleFinishLoadingFor(ModLoaderModContainer mc, Minecraft game) + { + FMLLog.finer("Handling post startup activities for ModLoader mod %s", mc.getModId()); + BaseMod mod = (BaseMod) mc.getMod(); + + Map, Render> renderers = Maps.newHashMap(RenderManager.instance.entityRenderMap); + + try + { + FMLLog.finest("Requesting renderers from basemod %s", mc.getModId()); + mod.addRenderer(renderers); + FMLLog.finest("Received %d renderers from basemod %s", renderers.size(), mc.getModId()); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "A severe problem was detected with the mod %s during the addRenderer call. Continuing, but expect odd results", mc.getModId()); + } + + MapDifference, Render> difference = Maps.difference(RenderManager.instance.entityRenderMap, renderers, Equivalences.identity()); + + for ( Entry, Render> e : difference.entriesOnlyOnLeft().entrySet()) + { + FMLLog.warning("The mod %s attempted to remove an entity renderer %s from the entity map. This will be ignored.", mc.getModId(), e.getKey().getName()); + } + + for (Entry, Render> e : difference.entriesOnlyOnRight().entrySet()) + { + FMLLog.finest("Registering ModLoader entity renderer %s as instance of %s", e.getKey().getName(), e.getValue().getClass().getName()); + RenderingRegistry.registerEntityRenderingHandler(e.getKey(), e.getValue()); + } + + for (Entry, ValueDifference> e : difference.entriesDiffering().entrySet()) + { + FMLLog.finest("Registering ModLoader entity rendering override for %s as instance of %s", e.getKey().getName(), e.getValue().rightValue().getClass().getName()); + RenderingRegistry.registerEntityRenderingHandler(e.getKey(), e.getValue().rightValue()); + } + + try + { + mod.registerAnimation(game); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "A severe problem was detected with the mod %s during the registerAnimation call. Continuing, but expect odd results", mc.getModId()); + } + } + + public ModLoaderClientHelper(Minecraft client) + { + this.client = client; + ModLoaderHelper.sidedHelper = this; + keyBindingContainers = Multimaps.newMultimap(Maps.>newHashMap(), new Supplier>() + { + @Override + public Collection get() + { + return Collections.singleton(new ModLoaderKeyBindingHandler()); + } + }); + } + + private Minecraft client; + private static Multimap keyBindingContainers; + + @Override + public void finishModLoading(ModLoaderModContainer mc) + { + handleFinishLoadingFor(mc, client); + } + + + public static void registerKeyBinding(BaseModProxy mod, KeyBinding keyHandler, boolean allowRepeat) + { + ModLoaderModContainer mlmc = (ModLoaderModContainer) Loader.instance().activeModContainer(); + ModLoaderKeyBindingHandler handler = Iterables.getOnlyElement(keyBindingContainers.get(mlmc)); + handler.setModContainer(mlmc); + handler.addKeyBinding(keyHandler, allowRepeat); + KeyBindingRegistry.registerKeyBinding(handler); + } + + + @Override + public Object getClientGui(BaseModProxy mod, EntityPlayer player, int ID, int x, int y, int z) + { + return ((net.minecraft.src.BaseMod)mod).getContainerGUI((EntityClientPlayerMP) player, ID, x, y, z); + } + + + @Override + public Entity spawnEntity(BaseModProxy mod, EntitySpawnPacket input, EntityRegistration er) + { + return ((net.minecraft.src.BaseMod)mod).spawnEntity(er.getModEntityId(), client.theWorld, input.scaledX, input.scaledY, input.scaledZ); + } + + + @Override + public void sendClientPacket(BaseModProxy mod, Packet250CustomPayload packet) + { + ((net.minecraft.src.BaseMod)mod).clientCustomPayload(client.thePlayer.sendQueue, packet); + } + + private Map managerLookups = new MapMaker().weakKeys().weakValues().makeMap(); + @Override + public void clientConnectionOpened(NetHandler netClientHandler, INetworkManager manager, BaseModProxy mod) + { + managerLookups.put(manager, netClientHandler); + ((BaseMod)mod).clientConnect((NetClientHandler)netClientHandler); + } + + + @Override + public boolean clientConnectionClosed(INetworkManager manager, BaseModProxy mod) + { + if (managerLookups.containsKey(manager)) + { + ((BaseMod)mod).clientDisconnect((NetClientHandler) managerLookups.get(manager)); + return true; + } + return false; + } +} diff --git a/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderKeyBindingHandler.java b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderKeyBindingHandler.java new file mode 100644 index 0000000..c4093db --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/modloader/ModLoaderKeyBindingHandler.java @@ -0,0 +1,112 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.client.modloader; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +import org.lwjgl.input.Mouse; + +import com.google.common.collect.ObjectArrays; +import com.google.common.primitives.Booleans; + +import net.minecraft.client.settings.KeyBinding; +import cpw.mods.fml.client.registry.KeyBindingRegistry; +import cpw.mods.fml.common.TickType; +import cpw.mods.fml.common.modloader.ModLoaderModContainer; + +/** + * @author cpw + * + */ +public class ModLoaderKeyBindingHandler extends KeyBindingRegistry.KeyHandler +{ + private ModLoaderModContainer modContainer; + private List helper; + private boolean[] active = new boolean[0]; + private boolean[] mlRepeats = new boolean[0]; + private boolean[] armed = new boolean[0]; + + public ModLoaderKeyBindingHandler() + { + super(new KeyBinding[0], new boolean[0]); + } + + void setModContainer(ModLoaderModContainer modContainer) + { + this.modContainer = modContainer; + } + + public void fireKeyEvent(KeyBinding kb) + { + ((net.minecraft.src.BaseMod)modContainer.getMod()).keyboardEvent(kb); + } + + @Override + public void keyDown(EnumSet type, KeyBinding kb, boolean end, boolean repeats) + { + if (!end) + { + return; + } + int idx = helper.indexOf(kb); + if (type.contains(TickType.CLIENT)) + { + armed[idx] = true; + } + if (armed[idx] && type.contains(TickType.RENDER) && (!active[idx] || mlRepeats[idx])) + { + fireKeyEvent(kb); + active[idx] = true; + armed[idx] = false; + } + } + + @Override + public void keyUp(EnumSet type, KeyBinding kb, boolean end) + { + if (!end) + { + return; + } + int idx = helper.indexOf(kb); + active[idx] = false; + } + + @Override + public EnumSet ticks() + { + return EnumSet.of(TickType.CLIENT, TickType.RENDER); + } + + @Override + public String getLabel() + { + return modContainer.getModId() +" KB "+keyBindings[0].keyCode; + } + + void addKeyBinding(KeyBinding binding, boolean repeats) + { + this.keyBindings = ObjectArrays.concat(this.keyBindings, binding); + this.repeatings = new boolean[this.keyBindings.length]; + Arrays.fill(this.repeatings, true); + this.active = new boolean[this.keyBindings.length]; + this.armed = new boolean[this.keyBindings.length]; + this.mlRepeats = Booleans.concat(this.mlRepeats, new boolean[] { repeats }); + this.keyDown = new boolean[this.keyBindings.length]; + this.helper = Arrays.asList(this.keyBindings); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/registry/ClientRegistry.java b/src/minecraft/cpw/mods/fml/client/registry/ClientRegistry.java new file mode 100644 index 0000000..36e8def --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/registry/ClientRegistry.java @@ -0,0 +1,29 @@ +package cpw.mods.fml.client.registry; + +import cpw.mods.fml.common.registry.GameRegistry; +import net.minecraft.client.renderer.tileentity.TileEntityRenderer; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.tileentity.TileEntity; + +public class ClientRegistry +{ + /** + * + * Utility method for registering a tile entity and it's renderer at once - generally you should register them separately + * + * @param tileEntityClass + * @param id + * @param specialRenderer + */ + public static void registerTileEntity(Class tileEntityClass, String id, TileEntitySpecialRenderer specialRenderer) + { + GameRegistry.registerTileEntity(tileEntityClass, id); + bindTileEntitySpecialRenderer(tileEntityClass, specialRenderer); + } + + public static void bindTileEntitySpecialRenderer(Class tileEntityClass, TileEntitySpecialRenderer specialRenderer) + { + TileEntityRenderer.instance.specialRendererMap.put(tileEntityClass, specialRenderer); + specialRenderer.setTileEntityRenderer(TileEntityRenderer.instance); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler.java b/src/minecraft/cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler.java new file mode 100644 index 0000000..d8e76d2 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler.java @@ -0,0 +1,16 @@ +package cpw.mods.fml.client.registry; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.world.IBlockAccess; + +public interface ISimpleBlockRenderingHandler +{ + public abstract void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer); + + public abstract boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer); + + public abstract boolean shouldRender3DInInventory(); + + public abstract int getRenderId(); +} diff --git a/src/minecraft/cpw/mods/fml/client/registry/KeyBindingRegistry.java b/src/minecraft/cpw/mods/fml/client/registry/KeyBindingRegistry.java new file mode 100644 index 0000000..f3c55bf --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/registry/KeyBindingRegistry.java @@ -0,0 +1,187 @@ +package cpw.mods.fml.client.registry; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.Set; + +import net.minecraft.client.settings.GameSettings; +import net.minecraft.client.settings.KeyBinding; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import cpw.mods.fml.common.ITickHandler; +import cpw.mods.fml.common.TickType; +import cpw.mods.fml.common.registry.TickRegistry; +import cpw.mods.fml.relauncher.Side; + +public class KeyBindingRegistry +{ + /** + * Register a KeyHandler to the game. This handler will be called on certain tick events + * if any of its key is inactive or has recently changed state + * + * @param handler + */ + public static void registerKeyBinding(KeyHandler handler) { + instance().keyHandlers.add(handler); + if (!handler.isDummy) + { + TickRegistry.registerTickHandler(handler, Side.CLIENT); + } + } + + + /** + * Extend this class to register a KeyBinding and recieve callback + * when the key binding is triggered + * + * @author cpw + * + */ + public static abstract class KeyHandler implements ITickHandler + { + protected KeyBinding[] keyBindings; + protected boolean[] keyDown; + protected boolean[] repeatings; + private boolean isDummy; + + /** + * Pass an array of keybindings and a repeat flag for each one + * + * @param keyBindings + * @param repeatings + */ + public KeyHandler(KeyBinding[] keyBindings, boolean[] repeatings) + { + assert keyBindings.length == repeatings.length : "You need to pass two arrays of identical length"; + this.keyBindings = keyBindings; + this.repeatings = repeatings; + this.keyDown = new boolean[keyBindings.length]; + } + + + /** + * Register the keys into the system. You will do your own keyboard management elsewhere. No events will fire + * if you use this method + * + * @param keyBindings + */ + public KeyHandler(KeyBinding[] keyBindings) + { + this.keyBindings = keyBindings; + this.isDummy = true; + } + + public KeyBinding[] getKeyBindings() + { + return this.keyBindings; + } + + /** + * Not to be overridden - KeyBindings are tickhandlers under the covers + */ + @Override + public final void tickStart(EnumSet type, Object... tickData) + { + keyTick(type, false); + } + + /** + * Not to be overridden - KeyBindings are tickhandlers under the covers + */ + @Override + public final void tickEnd(EnumSet type, Object... tickData) + { + keyTick(type, true); + } + + private void keyTick(EnumSet type, boolean tickEnd) + { + for (int i = 0; i < keyBindings.length; i++) + { + KeyBinding keyBinding = keyBindings[i]; + int keyCode = keyBinding.keyCode; + boolean state = (keyCode < 0 ? Mouse.isButtonDown(keyCode + 100) : Keyboard.isKeyDown(keyCode)); + if (state != keyDown[i] || (state && repeatings[i])) + { + if (state) + { + keyDown(type, keyBinding, tickEnd, state!=keyDown[i]); + } + else + { + keyUp(type, keyBinding, tickEnd); + } + if (tickEnd) + { + keyDown[i] = state; + } + } + + } + } + + /** + * Called when the key is first in the down position on any tick from the {@link #ticks()} + * set. Will be called subsequently with isRepeat set to true + * + * @see #keyUp(EnumSet, KeyBinding, boolean) + * + * @param types the type(s) of tick that fired when this key was first down + * @param tickEnd was it an end or start tick which fired the key + * @param isRepeat is it a repeat key event + */ + public abstract void keyDown(EnumSet types, KeyBinding kb, boolean tickEnd, boolean isRepeat); + /** + * Fired once when the key changes state from down to up + * + * @see #keyDown(EnumSet, KeyBinding, boolean, boolean) + * + * @param types the type(s) of tick that fired when this key was first down + * @param tickEnd was it an end or start tick which fired the key + */ + public abstract void keyUp(EnumSet types, KeyBinding kb, boolean tickEnd); + + + /** + * This is the list of ticks for which the key binding should trigger. The only + * valid ticks are client side ticks, obviously. + * + * @see cpw.mods.fml.common.ITickHandler#ticks() + */ + public abstract EnumSet ticks(); + } + + private static final KeyBindingRegistry INSTANCE = new KeyBindingRegistry(); + + private Set keyHandlers = Sets.newLinkedHashSet(); + + @Deprecated + public static KeyBindingRegistry instance() + { + return INSTANCE; + } + + + public void uploadKeyBindingsToGame(GameSettings settings) + { + ArrayList harvestedBindings = Lists.newArrayList(); + for (KeyHandler key : keyHandlers) + { + for (KeyBinding kb : key.keyBindings) + { + harvestedBindings.add(kb); + } + } + KeyBinding[] modKeyBindings = harvestedBindings.toArray(new KeyBinding[harvestedBindings.size()]); + KeyBinding[] allKeys = new KeyBinding[settings.keyBindings.length + modKeyBindings.length]; + System.arraycopy(settings.keyBindings, 0, allKeys, 0, settings.keyBindings.length); + System.arraycopy(modKeyBindings, 0, allKeys, settings.keyBindings.length, modKeyBindings.length); + settings.keyBindings = allKeys; + settings.loadOptions(); + } +} diff --git a/src/minecraft/cpw/mods/fml/client/registry/RenderingRegistry.java b/src/minecraft/cpw/mods/fml/client/registry/RenderingRegistry.java new file mode 100644 index 0000000..5406f60 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/client/registry/RenderingRegistry.java @@ -0,0 +1,166 @@ +package cpw.mods.fml.client.registry; + +import java.util.List; +import java.util.Map; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.entity.*; +import net.minecraft.entity.Entity; +import net.minecraft.world.IBlockAccess; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.ObjectArrays; + +import cpw.mods.fml.client.SpriteHelper; +import cpw.mods.fml.client.TextureFXManager; + +/** + * @author cpw + * + */ +public class RenderingRegistry +{ + private static final RenderingRegistry INSTANCE = new RenderingRegistry(); + + private int nextRenderId = 36; + + private Map blockRenderers = Maps.newHashMap(); + + private List entityRenderers = Lists.newArrayList(); + + /** + * Add a new armour prefix to the RenderPlayer + * + * @param armor + */ + public static int addNewArmourRendererPrefix(String armor) + { + RenderPlayer.armorFilenamePrefix = ObjectArrays.concat(RenderPlayer.armorFilenamePrefix, armor); + RenderBiped.bipedArmorFilenamePrefix = RenderPlayer.armorFilenamePrefix; + return RenderPlayer.armorFilenamePrefix.length - 1; + } + + /** + * Register an entity rendering handler. This will, after mod initialization, be inserted into the main + * render map for entities + * + * @param entityClass + * @param renderer + */ + public static void registerEntityRenderingHandler(Class entityClass, Render renderer) + { + instance().entityRenderers.add(new EntityRendererInfo(entityClass, renderer)); + } + + /** + * Register a simple block rendering handler + * + * @param handler + */ + public static void registerBlockHandler(ISimpleBlockRenderingHandler handler) + { + instance().blockRenderers.put(handler.getRenderId(), handler); + } + + /** + * Register the simple block rendering handler + * This version will not call getRenderId on the passed in handler, instead using the supplied ID, so you + * can easily re-use the same rendering handler for multiple IDs + * + * @param renderId + * @param handler + */ + public static void registerBlockHandler(int renderId, ISimpleBlockRenderingHandler handler) + { + instance().blockRenderers.put(renderId, handler); + } + /** + * Get the next available renderId from the block render ID list + */ + public static int getNextAvailableRenderId() + { + return instance().nextRenderId++; + } + + /** + * Add a texture override for the given path and return the used index + * + * @param fileToOverride + * @param fileToAdd + */ + public static int addTextureOverride(String fileToOverride, String fileToAdd) + { + int idx = SpriteHelper.getUniqueSpriteIndex(fileToOverride); + addTextureOverride(fileToOverride, fileToAdd, idx); + return idx; + } + + /** + * Add a texture override for the given path and index + * + * @param path + * @param overlayPath + * @param index + */ + public static void addTextureOverride(String path, String overlayPath, int index) + { + TextureFXManager.instance().addNewTextureOverride(path, overlayPath, index); + } + + /** + * Get and reserve a unique texture index for the supplied path + * + * @param path + */ + public static int getUniqueTextureIndex(String path) + { + return SpriteHelper.getUniqueSpriteIndex(path); + } + + @Deprecated public static RenderingRegistry instance() + { + return INSTANCE; + } + + private static class EntityRendererInfo + { + public EntityRendererInfo(Class target, Render renderer) + { + this.target = target; + this.renderer = renderer; + } + private Class target; + private Render renderer; + } + + public boolean renderWorldBlock(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, Block block, int modelId) + { + if (!blockRenderers.containsKey(modelId)) { return false; } + ISimpleBlockRenderingHandler bri = blockRenderers.get(modelId); + return bri.renderWorldBlock(world, x, y, z, block, modelId, renderer); + } + + public void renderInventoryBlock(RenderBlocks renderer, Block block, int metadata, int modelID) + { + if (!blockRenderers.containsKey(modelID)) { return; } + ISimpleBlockRenderingHandler bri = blockRenderers.get(modelID); + bri.renderInventoryBlock(block, metadata, modelID, renderer); + } + + public boolean renderItemAsFull3DBlock(int modelId) + { + ISimpleBlockRenderingHandler bri = blockRenderers.get(modelId); + return bri != null && bri.shouldRender3DInInventory(); + } + + public void loadEntityRenderers(Map, Render> rendererMap) + { + for (EntityRendererInfo info : entityRenderers) + { + rendererMap.put(info.target, info.renderer); + info.renderer.setRenderManager(RenderManager.instance); + } + } +} diff --git a/src/minecraft/cpw/mods/fml/common/BukkitPluginRef.java b/src/minecraft/cpw/mods/fml/common/BukkitPluginRef.java new file mode 100644 index 0000000..6d97f7b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/BukkitPluginRef.java @@ -0,0 +1,29 @@ +package cpw.mods.fml.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declare a variable to be populated by a Bukkit Plugin proxy instance if the bukkit coremod + * is available. It can only be applied to field typed as {@link BukkitProxy} + * Generally it should be used in conjunction with {@link Mod#bukkitPlugin()} specifying the + * plugin to load. + * + * @author cpw + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface BukkitPluginRef +{ + /** + * A reference (possibly version specific) to a Bukkit Plugin by name, using the name@versionbound + * specification. If this is a bukkit enabled environment the field annotated by this + * will be populated with a {@link BukkitProxy} instance if possible. This proxy will be gotten by + * reflectively calling the "getModProxy" method on the bukkit plugin instance. + * @return The name of the plugin which we will inject into this field + */ + String value(); +} diff --git a/src/minecraft/cpw/mods/fml/common/BukkitProxy.java b/src/minecraft/cpw/mods/fml/common/BukkitProxy.java new file mode 100644 index 0000000..d0dcfb3 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/BukkitProxy.java @@ -0,0 +1,12 @@ +package cpw.mods.fml.common; + +/** + * A marker interface for retrieving a proxy to a bukkit plugin. + * Fields associated with {@link BukkitPluginRef} annotations should be should probably + * declare this type and cast down if the target is available (not null) + * @author cpw + * + */ +public interface BukkitProxy +{ +} diff --git a/src/minecraft/cpw/mods/fml/common/CertificateHelper.java b/src/minecraft/cpw/mods/fml/common/CertificateHelper.java new file mode 100644 index 0000000..886346c --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/CertificateHelper.java @@ -0,0 +1,56 @@ +package cpw.mods.fml.common; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.cert.Certificate; + +public class CertificateHelper { + + private static final String HEXES = "0123456789abcdef"; + + public static String getFingerprint(Certificate certificate) + { + if (certificate == null) + { + return "NO VALID CERTIFICATE FOUND"; + } + try + { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] der = certificate.getEncoded(); + md.update(der); + byte[] digest = md.digest(); + return hexify(digest); + } + catch (Exception e) + { + return null; + } + } + + public static String getFingerprint(ByteBuffer buffer) + { + try + { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(buffer); + byte[] chksum = digest.digest(); + return hexify(chksum); + } + catch (Exception e) + { + return null; + } + } + + private static String hexify(byte[] chksum) + { + final StringBuilder hex = new StringBuilder( 2 * chksum.length ); + for ( final byte b : chksum ) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)) + .append(HEXES.charAt((b & 0x0F))); + } + return hex.toString(); + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/DummyModContainer.java b/src/minecraft/cpw/mods/fml/common/DummyModContainer.java new file mode 100644 index 0000000..61fca24 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/DummyModContainer.java @@ -0,0 +1,148 @@ +package cpw.mods.fml.common; + +import java.io.File; +import java.security.cert.Certificate; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import com.google.common.eventbus.EventBus; + +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionRange; + +public class DummyModContainer implements ModContainer +{ + private ModMetadata md; + private ArtifactVersion processedVersion; + + public DummyModContainer(ModMetadata md) + { + this.md = md; + } + + public DummyModContainer() + { + } + + @Override + public void bindMetadata(MetadataCollection mc) + { + } + + @Override + public List getDependants() + { + return Collections.emptyList(); + } + + @Override + public List getDependencies() + { + return Collections.emptyList(); + } + + @Override + public Set getRequirements() + { + return Collections.emptySet(); + } + + @Override + public ModMetadata getMetadata() + { + return md; + } + + @Override + public Object getMod() + { + return null; + } + + @Override + public String getModId() + { + return md.modId; + } + + @Override + public String getName() + { + return md.name; + } + + @Override + public String getSortingRules() + { + return ""; + } + + @Override + public File getSource() + { + return null; + } + + @Override + public String getVersion() + { + return md.version; + } + + public boolean matches(Object mod) + { + return false; + } + + @Override + public void setEnabledState(boolean enabled) + { + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) + { + return false; + } + + @Override + public ArtifactVersion getProcessedVersion() + { + if (processedVersion == null) + { + processedVersion = new DefaultArtifactVersion(getModId(), getVersion()); + } + return processedVersion; + } + + @Override + public boolean isImmutable() + { + return false; + } + + @Override + public boolean isNetworkMod() + { + return false; + } + + @Override + public String getDisplayVersion() + { + return md.version; + } + @Override + public VersionRange acceptableMinecraftVersionRange() + { + return Loader.instance().getMinecraftModContainer().getStaticVersionRange(); + } + + @Override + public Certificate getSigningCertificate() + { + return null; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/DuplicateModsFoundException.java b/src/minecraft/cpw/mods/fml/common/DuplicateModsFoundException.java new file mode 100644 index 0000000..a6105cd --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/DuplicateModsFoundException.java @@ -0,0 +1,16 @@ +package cpw.mods.fml.common; + +import java.io.File; +import java.util.List; + +import com.google.common.collect.SetMultimap; + +public class DuplicateModsFoundException extends LoaderException { + + public SetMultimap dupes; + + public DuplicateModsFoundException(SetMultimap dupes) { + this.dupes = dupes; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/FMLCommonHandler.java b/src/minecraft/cpw/mods/fml/common/FMLCommonHandler.java new file mode 100644 index 0000000..7f7e8ca --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/FMLCommonHandler.java @@ -0,0 +1,461 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; + +import net.minecraft.crash.CrashReport; +import net.minecraft.crash.CrashReportCategory; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.INetworkManager; +import net.minecraft.network.packet.NetHandler; +import net.minecraft.network.packet.Packet131MapData; +import net.minecraft.server.*; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.World; +import net.minecraft.world.storage.SaveHandler; +import net.minecraft.world.storage.WorldInfo; + +import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.Lists; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; +import cpw.mods.fml.common.network.EntitySpawnPacket; +import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.common.registry.ItemData; +import cpw.mods.fml.common.registry.TickRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.server.FMLServerHandler; + + +/** + * The main class for non-obfuscated hook handling code + * + * Anything that doesn't require obfuscated or client/server specific code should + * go in this handler + * + * It also contains a reference to the sided handler instance that is valid + * allowing for common code to access specific properties from the obfuscated world + * without a direct dependency + * + * @author cpw + * + */ +public class FMLCommonHandler +{ + /** + * The singleton + */ + private static final FMLCommonHandler INSTANCE = new FMLCommonHandler(); + /** + * The delegate for side specific data and functions + */ + private IFMLSidedHandler sidedDelegate; + + private List scheduledClientTicks = Lists.newArrayList(); + private List scheduledServerTicks = Lists.newArrayList(); + private Class forge; + private boolean noForge; + private List brandings; + private List crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation()); + private Set handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().makeMap()); + + + + public void beginLoading(IFMLSidedHandler handler) + { + sidedDelegate = handler; + FMLLog.info("Attempting early MinecraftForge initialization"); + callForgeMethod("initialize"); + callForgeMethod("registerCrashCallable"); + FMLLog.info("Completed early MinecraftForge initialization"); + } + + public void rescheduleTicks(Side side) + { + TickRegistry.updateTickQueue(side.isClient() ? scheduledClientTicks : scheduledServerTicks, side); + } + public void tickStart(EnumSet ticks, Side side, Object ... data) + { + List scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks; + + if (scheduledTicks.size()==0) + { + return; + } + for (IScheduledTickHandler ticker : scheduledTicks) + { + EnumSet ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class))); + ticksToRun.removeAll(EnumSet.complementOf(ticks)); + if (!ticksToRun.isEmpty()) + { + ticker.tickStart(ticksToRun, data); + } + } + } + + public void tickEnd(EnumSet ticks, Side side, Object ... data) + { + List scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks; + + if (scheduledTicks.size()==0) + { + return; + } + for (IScheduledTickHandler ticker : scheduledTicks) + { + EnumSet ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class))); + ticksToRun.removeAll(EnumSet.complementOf(ticks)); + if (!ticksToRun.isEmpty()) + { + ticker.tickEnd(ticksToRun, data); + } + } + } + + /** + * @return the instance + */ + public static FMLCommonHandler instance() + { + return INSTANCE; + } + /** + * Find the container that associates with the supplied mod object + * @param mod + */ + public ModContainer findContainerFor(Object mod) + { + return Loader.instance().getReversedModObjectList().get(mod); + } + /** + * Get the forge mod loader logging instance (goes to the forgemodloader log file) + * @return The log instance for the FML log file + */ + public Logger getFMLLogger() + { + return FMLLog.getLogger(); + } + + public Side getSide() + { + return sidedDelegate.getSide(); + } + + /** + * Return the effective side for the context in the game. This is dependent + * on thread analysis to try and determine whether the code is running in the + * server or not. Use at your own risk + */ + public Side getEffectiveSide() + { + Thread thr = Thread.currentThread(); + if ((thr instanceof ThreadMinecraftServer) || (thr instanceof ServerListenThread)) + { + return Side.SERVER; + } + + return Side.CLIENT; + } + /** + * Raise an exception + */ + public void raiseException(Throwable exception, String message, boolean stopGame) + { + FMLCommonHandler.instance().getFMLLogger().throwing("FMLHandler", "raiseException", exception); + if (stopGame) + { + getSidedDelegate().haltGame(message,exception); + } + } + + + private Class findMinecraftForge() + { + if (forge==null && !noForge) + { + try { + forge = Class.forName("net.minecraftforge.common.MinecraftForge"); + } catch (Exception ex) { + noForge = true; + } + } + return forge; + } + + private Object callForgeMethod(String method) + { + if (noForge) + return null; + try + { + return findMinecraftForge().getMethod(method).invoke(null); + } + catch (Exception e) + { + // No Forge installation + return null; + } + } + + public void computeBranding() + { + if (brandings == null) + { + Builder brd = ImmutableList.builder(); + brd.add(Loader.instance().getMCVersionString()); + brd.add(Loader.instance().getMCPVersionString()); + brd.add("FML v"+Loader.instance().getFMLVersionString()); + String forgeBranding = (String) callForgeMethod("getBrandingVersion"); + if (!Strings.isNullOrEmpty(forgeBranding)) + { + brd.add(forgeBranding); + } + if (sidedDelegate!=null) + { + brd.addAll(sidedDelegate.getAdditionalBrandingInformation()); + } + try { + Properties props=new Properties(); + props.load(getClass().getClassLoader().getResourceAsStream("fmlbranding.properties")); + brd.add(props.getProperty("fmlbranding")); + } catch (Exception ex) { + // Ignore - no branding file found + } + int tModCount = Loader.instance().getModList().size(); + int aModCount = Loader.instance().getActiveModList().size(); + brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" )); + brandings = brd.build(); + } + } + public List getBrandings() + { + if (brandings == null) + { + computeBranding(); + } + return ImmutableList.copyOf(brandings); + } + + public IFMLSidedHandler getSidedDelegate() + { + return sidedDelegate; + } + + public void onPostServerTick() + { + tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER); + } + + /** + * Every tick just after world and other ticks occur + */ + public void onPostWorldTick(Object world) + { + tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world); + } + + public void onPreServerTick() + { + tickStart(EnumSet.of(TickType.SERVER), Side.SERVER); + } + + /** + * Every tick just before world and other ticks occur + */ + public void onPreWorldTick(Object world) + { + tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world); + } + + public void onWorldLoadTick(World[] worlds) + { + rescheduleTicks(Side.SERVER); + for (World w : worlds) + { + tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w); + } + } + + public void handleServerStarting(MinecraftServer server) + { + Loader.instance().serverStarting(server); + } + + public void handleServerStarted() + { + Loader.instance().serverStarted(); + } + + public void handleServerStopping() + { + Loader.instance().serverStopping(); + } + + public MinecraftServer getMinecraftServerInstance() + { + return sidedDelegate.getServer(); + } + + public void showGuiScreen(Object clientGuiElement) + { + sidedDelegate.showGuiScreen(clientGuiElement); + } + + public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket) + { + return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket); + } + + public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket) + { + sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket); + } + + public void onServerStart(DedicatedServer dedicatedServer) + { + FMLServerHandler.instance(); + sidedDelegate.beginServerLoading(dedicatedServer); + } + + public void onServerStarted() + { + sidedDelegate.finishServerLoading(); + } + + + public void onPreClientTick() + { + tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT); + + } + + public void onPostClientTick() + { + tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT); + } + + public void onRenderTickStart(float timer) + { + tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer); + } + + public void onRenderTickEnd(float timer) + { + tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer); + } + + public void onPlayerPreTick(EntityPlayer player) + { + Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT; + tickStart(EnumSet.of(TickType.PLAYER), side, player); + } + + public void onPlayerPostTick(EntityPlayer player) + { + Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT; + tickEnd(EnumSet.of(TickType.PLAYER), side, player); + } + + public void registerCrashCallable(ICrashCallable callable) + { + crashCallables.add(callable); + } + + public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category) + { + for (ICrashCallable call: crashCallables) + { + category.addCrashSectionCallable(call.getLabel(), call); + } + } + + public void handleTinyPacket(NetHandler handler, Packet131MapData mapData) + { + sidedDelegate.handleTinyPacket(handler, mapData); + } + + public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) + { + for (ModContainer mc : Loader.instance().getModList()) + { + if (mc instanceof InjectedModContainer) + { + WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); + if (wac != null) + { + NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo); + tagCompound.setCompoundTag(mc.getModId(), dataForWriting); + } + } + } + } + + public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) + { + if (getEffectiveSide()!=Side.SERVER) + { + return; + } + if (handlerSet.contains(handler)) + { + return; + } + handlerSet.add(handler); + Map additionalProperties = Maps.newHashMap(); + worldInfo.setAdditionalProperties(additionalProperties); + for (ModContainer mc : Loader.instance().getModList()) + { + if (mc instanceof InjectedModContainer) + { + WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); + if (wac != null) + { + wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId())); + } + } + } + } + + public boolean shouldServerBeKilledQuietly() + { + return sidedDelegate.shouldServerShouldBeKilledQuietly(); + } + + public void disconnectIDMismatch(MapDifference serverDifference, NetHandler toKill, INetworkManager network) + { + sidedDelegate.disconnectIDMismatch(serverDifference, toKill, network); + } + + public void handleServerStopped() + { + Loader.instance().serverStopped(); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/FMLDummyContainer.java b/src/minecraft/cpw/mods/fml/common/FMLDummyContainer.java new file mode 100644 index 0000000..6be7080 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/FMLDummyContainer.java @@ -0,0 +1,127 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.world.storage.SaveHandler; +import net.minecraft.world.storage.WorldInfo; + +import com.google.common.eventbus.EventBus; + +import cpw.mods.fml.common.registry.GameData; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.registry.ItemData; + +/** + * @author cpw + * + */ +public class FMLDummyContainer extends DummyModContainer implements WorldAccessContainer +{ + public FMLDummyContainer() + { + super(new ModMetadata()); + ModMetadata meta = getMetadata(); + meta.modId="FML"; + meta.name="Forge Mod Loader"; + meta.version=Loader.instance().getFMLVersionString(); + meta.credits="Made possible with help from many people"; + meta.authorList=Arrays.asList("cpw, LexManos"); + meta.description="The Forge Mod Loader provides the ability for systems to load mods " + + "from the file system. It also provides key capabilities for mods to be able " + + "to cooperate and provide a good modding environment. " + + "The mod loading system is compatible with ModLoader, all your ModLoader " + + "mods should work."; + meta.url="https://github.com/cpw/FML/wiki"; + meta.updateUrl="https://github.com/cpw/FML/wiki"; + meta.screenshots=new String[0]; + meta.logoFile=""; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) + { + return true; + } + + @Override + public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info) + { + NBTTagCompound fmlData = new NBTTagCompound(); + NBTTagList list = new NBTTagList(); + for (ModContainer mc : Loader.instance().getActiveModList()) + { + NBTTagCompound mod = new NBTTagCompound(); + mod.setString("ModId", mc.getModId()); + mod.setString("ModVersion", mc.getVersion()); + list.appendTag(mod); + } + fmlData.setTag("ModList", list); + NBTTagList itemList = new NBTTagList(); + GameData.writeItemData(itemList); + fmlData.setTag("ModItemData", itemList); + return fmlData; + } + + @Override + public void readData(SaveHandler handler, WorldInfo info, Map propertyMap, NBTTagCompound tag) + { + if (tag.hasKey("ModList")) + { + NBTTagList modList = tag.getTagList("ModList"); + for (int i = 0; i < modList.tagCount(); i++) + { + NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i); + String modId = mod.getString("ModId"); + String modVersion = mod.getString("ModVersion"); + ModContainer container = Loader.instance().getIndexedModList().get(modId); + if (container == null) + { + FMLLog.severe("This world was saved with mod %s which appears to be missing, things may not work well", modId); + continue; + } + if (!modVersion.equals(container.getVersion())) + { + FMLLog.info("This world was saved with mod %s version %s and it is now at version %s, things may not work well", modId, modVersion, container.getVersion()); + } + } + } + if (tag.hasKey("ModItemData")) + { + NBTTagList modList = tag.getTagList("ModItemData"); + Set worldSaveItems = GameData.buildWorldItemData(modList); + GameData.validateWorldSave(worldSaveItems); + } + else + { + GameData.validateWorldSave(null); + } + } + + + @Override + public Certificate getSigningCertificate() + { + Certificate[] certificates = getClass().getProtectionDomain().getCodeSource().getCertificates(); + return certificates != null ? certificates[0] : null; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/FMLLog.java b/src/minecraft/cpw/mods/fml/common/FMLLog.java new file mode 100644 index 0000000..f9e6d67 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/FMLLog.java @@ -0,0 +1,53 @@ +package cpw.mods.fml.common; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FMLLog +{ + private static cpw.mods.fml.relauncher.FMLRelaunchLog coreLog = cpw.mods.fml.relauncher.FMLRelaunchLog.log; + + public static void log(Level level, String format, Object... data) + { + coreLog.log(level, format, data); + } + + public static void log(Level level, Throwable ex, String format, Object... data) + { + coreLog.log(level, ex, format, data); + } + + public static void severe(String format, Object... data) + { + log(Level.SEVERE, format, data); + } + + public static void warning(String format, Object... data) + { + log(Level.WARNING, format, data); + } + + public static void info(String format, Object... data) + { + log(Level.INFO, format, data); + } + + public static void fine(String format, Object... data) + { + log(Level.FINE, format, data); + } + + public static void finer(String format, Object... data) + { + log(Level.FINER, format, data); + } + + public static void finest(String format, Object... data) + { + log(Level.FINEST, format, data); + } + public static Logger getLogger() + { + return coreLog.getLogger(); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/FMLModContainer.java b/src/minecraft/cpw/mods/fml/common/FMLModContainer.java new file mode 100644 index 0000000..7ab6449 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/FMLModContainer.java @@ -0,0 +1,533 @@ +/* + * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.base.Throwables; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import cpw.mods.fml.common.Mod.Instance; +import cpw.mods.fml.common.Mod.Metadata; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; +import cpw.mods.fml.common.event.FMLConstructionEvent; +import cpw.mods.fml.common.event.FMLEvent; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLInterModComms.IMCEvent; +import cpw.mods.fml.common.event.FMLFingerprintViolationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStartedEvent; +import cpw.mods.fml.common.event.FMLServerStartingEvent; +import cpw.mods.fml.common.event.FMLServerStoppedEvent; +import cpw.mods.fml.common.event.FMLServerStoppingEvent; +import cpw.mods.fml.common.event.FMLStateEvent; +import cpw.mods.fml.common.network.FMLNetworkHandler; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.common.versioning.VersionRange; + +public class FMLModContainer implements ModContainer +{ + private Mod modDescriptor; + private Object modInstance; + private File source; + private ModMetadata modMetadata; + private String className; + private Map descriptor; + private boolean enabled = true; + private String internalVersion; + private boolean overridesMetadata; + private EventBus eventBus; + private LoadController controller; + private Multimap, Object> annotations; + private DefaultArtifactVersion processedVersion; + private boolean isNetworkMod; + + private static final BiMap, Class> modAnnotationTypes = ImmutableBiMap., Class>builder() + .put(FMLPreInitializationEvent.class, Mod.PreInit.class) + .put(FMLInitializationEvent.class, Mod.Init.class) + .put(FMLPostInitializationEvent.class, Mod.PostInit.class) + .put(FMLServerStartingEvent.class, Mod.ServerStarting.class) + .put(FMLServerStartedEvent.class, Mod.ServerStarted.class) + .put(FMLServerStoppingEvent.class, Mod.ServerStopping.class) + .put(FMLServerStoppedEvent.class, Mod.ServerStopped.class) + .put(IMCEvent.class,Mod.IMCCallback.class) + .put(FMLFingerprintViolationEvent.class, Mod.FingerprintWarning.class) + .build(); + private static final BiMap, Class> modTypeAnnotations = modAnnotationTypes.inverse(); + private String annotationDependencies; + private VersionRange minecraftAccepted; + private boolean fingerprintNotPresent; + private Set sourceFingerprints; + private Certificate certificate; + + + public FMLModContainer(String className, File modSource, Map modDescriptor) + { + this.className = className; + this.source = modSource; + this.descriptor = modDescriptor; + } + + @Override + public String getModId() + { + return (String) descriptor.get("modid"); + } + + @Override + public String getName() + { + return modMetadata.name; + } + + @Override + public String getVersion() + { + return internalVersion; + } + + @Override + public File getSource() + { + return source; + } + + @Override + public ModMetadata getMetadata() + { + return modMetadata; + } + + @Override + public void bindMetadata(MetadataCollection mc) + { + modMetadata = mc.getMetadataForId(getModId(), descriptor); + + if (descriptor.containsKey("useMetadata")) + { + overridesMetadata = !((Boolean)descriptor.get("useMetadata")).booleanValue(); + } + + if (overridesMetadata || !modMetadata.useDependencyInformation) + { + Set requirements = Sets.newHashSet(); + List dependencies = Lists.newArrayList(); + List dependants = Lists.newArrayList(); + annotationDependencies = (String) descriptor.get("dependencies"); + Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants); + modMetadata.requiredMods = requirements; + modMetadata.dependencies = dependencies; + modMetadata.dependants = dependants; + FMLLog.finest("Parsed dependency info : %s %s %s", requirements, dependencies, dependants); + } + else + { + FMLLog.finest("Using mcmod dependency info : %s %s %s", modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants); + } + if (Strings.isNullOrEmpty(modMetadata.name)) + { + FMLLog.info("Mod %s is missing the required element 'name'. Substituting %s", getModId(), getModId()); + modMetadata.name = getModId(); + } + internalVersion = (String) descriptor.get("version"); + if (Strings.isNullOrEmpty(internalVersion)) + { + Properties versionProps = searchForVersionProperties(); + if (versionProps != null) + { + internalVersion = versionProps.getProperty(getModId()+".version"); + FMLLog.fine("Found version %s for mod %s in version.properties, using", internalVersion, getModId()); + } + + } + if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version)) + { + FMLLog.warning("Mod %s is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version %s", getModId(), modMetadata.version); + internalVersion = modMetadata.version; + } + if (Strings.isNullOrEmpty(internalVersion)) + { + FMLLog.warning("Mod %s is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId()); + modMetadata.version = internalVersion = "1.0"; + } + + String mcVersionString = (String) descriptor.get("acceptedMinecraftVersions"); + if (!Strings.isNullOrEmpty(mcVersionString)) + { + minecraftAccepted = VersionParser.parseRange(mcVersionString); + } + else + { + minecraftAccepted = Loader.instance().getMinecraftModContainer().getStaticVersionRange(); + } + } + + public Properties searchForVersionProperties() + { + try + { + FMLLog.fine("Attempting to load the file version.properties from %s to locate a version number for %s", getSource().getName(), getModId()); + Properties version = null; + if (getSource().isFile()) + { + ZipFile source = new ZipFile(getSource()); + ZipEntry versionFile = source.getEntry("version.properties"); + if (versionFile!=null) + { + version = new Properties(); + version.load(source.getInputStream(versionFile)); + } + source.close(); + } + else if (getSource().isDirectory()) + { + File propsFile = new File(getSource(),"version.properties"); + if (propsFile.exists() && propsFile.isFile()) + { + version = new Properties(); + FileInputStream fis = new FileInputStream(propsFile); + version.load(fis); + fis.close(); + } + } + return version; + } + catch (Exception e) + { + Throwables.propagateIfPossible(e); + FMLLog.fine("Failed to find a usable version.properties file"); + return null; + } + } + + @Override + public void setEnabledState(boolean enabled) + { + this.enabled = enabled; + } + + @Override + public Set getRequirements() + { + return modMetadata.requiredMods; + } + + @Override + public List getDependencies() + { + return modMetadata.dependencies; + } + + @Override + public List getDependants() + { + return modMetadata.dependants; + } + + @Override + public String getSortingRules() + { + return ((overridesMetadata || !modMetadata.useDependencyInformation) ? Strings.nullToEmpty(annotationDependencies) : modMetadata.printableSortingRules()); + } + + @Override + public boolean matches(Object mod) + { + return mod == modInstance; + } + + @Override + public Object getMod() + { + return modInstance; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) + { + if (this.enabled) + { + FMLLog.fine("Enabling mod %s", getModId()); + this.eventBus = bus; + this.controller = controller; + eventBus.register(this); + return true; + } + else + { + return false; + } + } + + private Multimap, Object> gatherAnnotations(Class clazz) throws Exception + { + Multimap,Object> anns = ArrayListMultimap.create(); + + for (Method m : clazz.getDeclaredMethods()) + { + for (Annotation a : m.getAnnotations()) + { + if (modTypeAnnotations.containsKey(a.annotationType())) + { + Class[] paramTypes = new Class[] { modTypeAnnotations.get(a.annotationType()) }; + + if (Arrays.equals(m.getParameterTypes(), paramTypes)) + { + m.setAccessible(true); + anns.put(a.annotationType(), m); + } + else + { + FMLLog.severe("The mod %s appears to have an invalid method annotation %s. This annotation can only apply to methods with argument types %s -it will not be called", getModId(), a.annotationType().getSimpleName(), Arrays.toString(paramTypes)); + } + } + } + } + return anns; + } + + private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception + { + SetMultimap annotations = asmDataTable.getAnnotationsFor(this); + + parseSimpleFieldAnnotation(annotations, Instance.class.getName(), new Function() + { + public Object apply(ModContainer mc) + { + return mc.getMod(); + } + }); + parseSimpleFieldAnnotation(annotations, Metadata.class.getName(), new Function() + { + public Object apply(ModContainer mc) + { + return mc.getMetadata(); + } + }); + } + + private void parseSimpleFieldAnnotation(SetMultimap annotations, String annotationClassName, Function retreiver) throws IllegalAccessException + { + String[] annName = annotationClassName.split("\\."); + String annotationName = annName[annName.length - 1]; + for (ASMData targets : annotations.get(annotationClassName)) + { + String targetMod = (String) targets.getAnnotationInfo().get("value"); + Field f = null; + Object injectedMod = null; + ModContainer mc = this; + boolean isStatic = false; + Class clz = modInstance.getClass(); + if (!Strings.isNullOrEmpty(targetMod)) + { + if (Loader.isModLoaded(targetMod)) + { + mc = Loader.instance().getIndexedModList().get(targetMod); + } + else + { + mc = null; + } + } + if (mc != null) + { + try + { + clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader()); + f = clz.getDeclaredField(targets.getObjectName()); + f.setAccessible(true); + isStatic = Modifier.isStatic(f.getModifiers()); + injectedMod = retreiver.apply(mc); + } + catch (Exception e) + { + Throwables.propagateIfPossible(e); + FMLLog.log(Level.WARNING, e, "Attempting to load @%s in class %s for %s and failing", annotationName, targets.getClassName(), mc.getModId()); + } + } + if (f != null) + { + Object target = null; + if (!isStatic) + { + target = modInstance; + if (!modInstance.getClass().equals(clz)) + { + FMLLog.warning("Unable to inject @%s in non-static field %s.%s for %s as it is NOT the primary mod instance", annotationName, targets.getClassName(), targets.getObjectName(), mc.getModId()); + continue; + } + } + f.set(target, injectedMod); + } + } + } + + @Subscribe + public void constructMod(FMLConstructionEvent event) + { + try + { + ModClassLoader modClassLoader = event.getModClassLoader(); + modClassLoader.addFile(source); + Class clazz = Class.forName(className, true, modClassLoader); + + Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates(); + int len = 0; + if (certificates != null) + { + len = certificates.length; + } + Builder certBuilder = ImmutableList.builder(); + for (int i = 0; i < len; i++) + { + certBuilder.add(CertificateHelper.getFingerprint(certificates[i])); + } + + ImmutableList certList = certBuilder.build(); + sourceFingerprints = ImmutableSet.copyOf(certList); + + String expectedFingerprint = (String) descriptor.get("certificateFingerprint"); + + fingerprintNotPresent = true; + + if (expectedFingerprint != null && !expectedFingerprint.isEmpty()) + { + if (!sourceFingerprints.contains(expectedFingerprint)) + { + Level warnLevel = Level.SEVERE; + if (source.isDirectory()) + { + warnLevel = Level.FINER; + } + FMLLog.log(warnLevel, "The mod %s is expecting signature %s for source %s, however there is no signature matching that description", getModId(), expectedFingerprint, source.getName()); + } + else + { + certificate = certificates[certList.indexOf(expectedFingerprint)]; + fingerprintNotPresent = false; + } + } + + annotations = gatherAnnotations(clazz); + isNetworkMod = FMLNetworkHandler.instance().registerNetworkMod(this, clazz, event.getASMHarvestedData()); + modInstance = clazz.newInstance(); + if (fingerprintNotPresent) + { + eventBus.post(new FMLFingerprintViolationEvent(source.isDirectory(), source, ImmutableSet.copyOf(this.sourceFingerprints), expectedFingerprint)); + } + ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); + processFieldAnnotations(event.getASMHarvestedData()); + } + catch (Throwable e) + { + controller.errorOccurred(this, e); + Throwables.propagateIfPossible(e); + } + } + + @Subscribe + public void handleModStateEvent(FMLEvent event) + { + Class annotation = modAnnotationTypes.get(event.getClass()); + if (annotation == null) + { + return; + } + try + { + for (Object o : annotations.get(annotation)) + { + Method m = (Method) o; + m.invoke(modInstance, event); + } + } + catch (Throwable t) + { + controller.errorOccurred(this, t); + Throwables.propagateIfPossible(t); + } + } + + @Override + public ArtifactVersion getProcessedVersion() + { + if (processedVersion == null) + { + processedVersion = new DefaultArtifactVersion(getModId(), getVersion()); + } + return processedVersion; + } + @Override + public boolean isImmutable() + { + return false; + } + + @Override + public boolean isNetworkMod() + { + return isNetworkMod; + } + + @Override + public String getDisplayVersion() + { + return modMetadata.version; + } + + @Override + public VersionRange acceptableMinecraftVersionRange() + { + return minecraftAccepted; + } + + @Override + public Certificate getSigningCertificate() + { + return certificate; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/IConsoleHandler.java b/src/minecraft/cpw/mods/fml/common/IConsoleHandler.java new file mode 100644 index 0000000..5a5e1e0 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IConsoleHandler.java @@ -0,0 +1,24 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +/** + * @author cpw + * + */ +public interface IConsoleHandler +{ + public boolean handleCommand(String command, Object... data); +} diff --git a/src/minecraft/cpw/mods/fml/common/ICraftingHandler.java b/src/minecraft/cpw/mods/fml/common/ICraftingHandler.java new file mode 100644 index 0000000..03df7cc --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ICraftingHandler.java @@ -0,0 +1,43 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * Return a crafting handler for the mod container to call + * + * @author cpw + * + */ +public interface ICraftingHandler +{ + /** + * The object array contains these three arguments + * + * @param player + * @param item + * @param craftMatrix + */ + void onCrafting(EntityPlayer player, ItemStack item, IInventory craftMatrix); + + /** + * The object array contains these two arguments + * @param player + * @param item + */ + void onSmelting(EntityPlayer player, ItemStack item); +} diff --git a/src/minecraft/cpw/mods/fml/common/ICrashCallable.java b/src/minecraft/cpw/mods/fml/common/ICrashCallable.java new file mode 100644 index 0000000..ecd0586 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ICrashCallable.java @@ -0,0 +1,8 @@ +package cpw.mods.fml.common; + +import java.util.concurrent.Callable; + +public interface ICrashCallable extends Callable +{ + String getLabel(); +} diff --git a/src/minecraft/cpw/mods/fml/common/IDispenseHandler.java b/src/minecraft/cpw/mods/fml/common/IDispenseHandler.java new file mode 100644 index 0000000..7794882 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IDispenseHandler.java @@ -0,0 +1,48 @@ +/* + * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.util.Random; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + + +/** + * Deprecated without replacement, use vanilla DispenserRegistry code + * + * @author cpw + * + */ +@Deprecated +public interface IDispenseHandler +{ + /** + * Return -1 if you don't want to dispense anything. the other values seem to have specific meanings + * to blockdispenser. + * + * @param x + * @param y + * @param z + * @param xVelocity + * @param zVelocity + * @param world + * @param item + * @param random + * @param entX + * @param entY + * @param entZ + */ + @Deprecated + int dispense(double x, double y, double z, int xVelocity, int zVelocity, World world, ItemStack item, Random random, double entX, double entY, double entZ); +} diff --git a/src/minecraft/cpw/mods/fml/common/IDispenserHandler.java b/src/minecraft/cpw/mods/fml/common/IDispenserHandler.java new file mode 100644 index 0000000..acbb407 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IDispenserHandler.java @@ -0,0 +1,33 @@ +package cpw.mods.fml.common; + +import java.util.Random; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * + * Deprecated without replacement. Use vanilla DispenserRegistry code. + * + * @author cpw + * + */ +@Deprecated +public interface IDispenserHandler +{ + /** + * Called to dispense an entity + * @param x + * @param y + * @param z + * @param xVelocity + * @param zVelocity + * @param world + * @param item + * @param random + * @param entX + * @param entY + * @param entZ + */ + int dispense(int x, int y, int z, int xVelocity, int zVelocity, World world, ItemStack item, Random random, double entX, double entY, double entZ); +} diff --git a/src/minecraft/cpw/mods/fml/common/IFMLHandledException.java b/src/minecraft/cpw/mods/fml/common/IFMLHandledException.java new file mode 100644 index 0000000..a9f52bf --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IFMLHandledException.java @@ -0,0 +1,6 @@ +package cpw.mods.fml.common; + +public interface IFMLHandledException +{ + +} diff --git a/src/minecraft/cpw/mods/fml/common/IFMLSidedHandler.java b/src/minecraft/cpw/mods/fml/common/IFMLSidedHandler.java new file mode 100644 index 0000000..9f57b03 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IFMLSidedHandler.java @@ -0,0 +1,53 @@ +package cpw.mods.fml.common; + +import java.util.List; + +import com.google.common.collect.MapDifference; + +import net.minecraft.entity.Entity; +import net.minecraft.network.INetworkManager; +import net.minecraft.network.packet.NetHandler; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.Packet131MapData; +import net.minecraft.server.MinecraftServer; +import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; +import cpw.mods.fml.common.network.EntitySpawnPacket; +import cpw.mods.fml.common.network.ModMissingPacket; +import cpw.mods.fml.common.registry.ItemData; +import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.relauncher.Side; + +public interface IFMLSidedHandler +{ + List getAdditionalBrandingInformation(); + + Side getSide(); + + void haltGame(String message, Throwable exception); + + void showGuiScreen(Object clientGuiElement); + + Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket packet); + + void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket); + + void beginServerLoading(MinecraftServer server); + + void finishServerLoading(); + + MinecraftServer getServer(); + + void sendPacket(Packet packet); + + void displayMissingMods(ModMissingPacket modMissingPacket); + + void handleTinyPacket(NetHandler handler, Packet131MapData mapData); + + void setClientCompatibilityLevel(byte compatibilityLevel); + + byte getClientCompatibilityLevel(); + + boolean shouldServerShouldBeKilledQuietly(); + + void disconnectIDMismatch(MapDifference s, NetHandler toKill, INetworkManager mgr); +} diff --git a/src/minecraft/cpw/mods/fml/common/IFuelHandler.java b/src/minecraft/cpw/mods/fml/common/IFuelHandler.java new file mode 100644 index 0000000..67e8a7f --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IFuelHandler.java @@ -0,0 +1,8 @@ +package cpw.mods.fml.common; + +import net.minecraft.item.ItemStack; + +public interface IFuelHandler +{ + int getBurnTime(ItemStack fuel); +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/INetworkHandler.java b/src/minecraft/cpw/mods/fml/common/INetworkHandler.java new file mode 100644 index 0000000..b41a8d1 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/INetworkHandler.java @@ -0,0 +1,26 @@ +/* + * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + + + +/** + * @author cpw + * + */ +public interface INetworkHandler +{ + boolean onChat(Object... data); + void onPacket250Packet(Object... data); + void onServerLogin(Object handler); +} diff --git a/src/minecraft/cpw/mods/fml/common/IPickupNotifier.java b/src/minecraft/cpw/mods/fml/common/IPickupNotifier.java new file mode 100644 index 0000000..ad4221b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IPickupNotifier.java @@ -0,0 +1,22 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; + +public interface IPickupNotifier +{ + void notifyPickup(EntityItem item, EntityPlayer player); +} diff --git a/src/minecraft/cpw/mods/fml/common/IPlayerTracker.java b/src/minecraft/cpw/mods/fml/common/IPlayerTracker.java new file mode 100644 index 0000000..d380207 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IPlayerTracker.java @@ -0,0 +1,32 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import net.minecraft.entity.player.EntityPlayer; + +/** + * @author cpw + * + */ +public interface IPlayerTracker +{ + void onPlayerLogin(EntityPlayer player); + + void onPlayerLogout(EntityPlayer player); + + void onPlayerChangedDimension(EntityPlayer player); + + void onPlayerRespawn(EntityPlayer player); +} diff --git a/src/minecraft/cpw/mods/fml/common/IScheduledTickHandler.java b/src/minecraft/cpw/mods/fml/common/IScheduledTickHandler.java new file mode 100644 index 0000000..852ca1b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IScheduledTickHandler.java @@ -0,0 +1,12 @@ +package cpw.mods.fml.common; + +public interface IScheduledTickHandler extends ITickHandler +{ + /** + * Return the number of actual ticks that will pass + * before your next tick will fire. This will be called + * just after your last tick fired to compute the next delay. + * @return Time until next tick + */ + public int nextTickSpacing(); +} diff --git a/src/minecraft/cpw/mods/fml/common/ITickHandler.java b/src/minecraft/cpw/mods/fml/common/ITickHandler.java new file mode 100644 index 0000000..7e145e6 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ITickHandler.java @@ -0,0 +1,61 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import java.util.EnumSet; + + +/** + * + * Tick handler for mods to implement and register through the TickRegistry + * + * The data available to each tick is documented in the TickType + * + * @author cpw + * + */ +public interface ITickHandler +{ + + /** + * Called at the "start" phase of a tick + * + * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks + * + * @param type + * @param tickData + */ + public void tickStart(EnumSet type, Object... tickData); + + /** + * Called at the "end" phase of a tick + * + * Multiple ticks may fire simultaneously- you will only be called once with all the firing ticks + * + * @param type + * @param tickData + */ + public void tickEnd(EnumSet type, Object... tickData); + + /** + * Returns the list of ticks this tick handler is interested in receiving at the minute + */ + public EnumSet ticks(); + + /** + * A profiling label for this tick handler + */ + public String getLabel(); +} diff --git a/src/minecraft/cpw/mods/fml/common/IWorldGenerator.java b/src/minecraft/cpw/mods/fml/common/IWorldGenerator.java new file mode 100644 index 0000000..1520e55 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/IWorldGenerator.java @@ -0,0 +1,42 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.util.Random; + +import net.minecraft.world.World; +import net.minecraft.world.chunk.IChunkProvider; + + +/** + * This is called back during world generation. + * + * @author cpw + * + */ +public interface IWorldGenerator +{ + /** + * Generate some world + * + * @param random the chunk specific {@link Random}. + * @param chunkX the chunk X coordinate of this chunk. + * @param chunkZ the chunk Z coordinate of this chunk. + * @param world : additionalData[0] The minecraft {@link World} we're generating for. + * @param chunkGenerator : additionalData[1] The {@link IChunkProvider} that is generating. + * @param chunkProvider : additionalData[2] {@link IChunkProvider} that is requesting the world generation. + * + */ + public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider); +} diff --git a/src/minecraft/cpw/mods/fml/common/InjectedModContainer.java b/src/minecraft/cpw/mods/fml/common/InjectedModContainer.java new file mode 100644 index 0000000..5c51028 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/InjectedModContainer.java @@ -0,0 +1,139 @@ +package cpw.mods.fml.common; + +import java.io.File; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Set; + +import com.google.common.eventbus.EventBus; + +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionRange; + +public class InjectedModContainer implements ModContainer +{ + private File source; + private ModContainer wrappedContainer; + + public InjectedModContainer(ModContainer mc, File source) + { + this.source = source; + this.wrappedContainer = mc; + } + + public String getModId() + { + return wrappedContainer.getModId(); + } + + public String getName() + { + return wrappedContainer.getName(); + } + + public String getVersion() + { + return wrappedContainer.getVersion(); + } + + public File getSource() + { + return source; + } + + public ModMetadata getMetadata() + { + return wrappedContainer.getMetadata(); + } + + public void bindMetadata(MetadataCollection mc) + { + wrappedContainer.bindMetadata(mc); + } + + public void setEnabledState(boolean enabled) + { + wrappedContainer.setEnabledState(enabled); + } + + public Set getRequirements() + { + return wrappedContainer.getRequirements(); + } + + public List getDependencies() + { + return wrappedContainer.getDependencies(); + } + + public List getDependants() + { + return wrappedContainer.getDependants(); + } + + public String getSortingRules() + { + return wrappedContainer.getSortingRules(); + } + + public boolean registerBus(EventBus bus, LoadController controller) + { + return wrappedContainer.registerBus(bus, controller); + } + + public boolean matches(Object mod) + { + return wrappedContainer.matches(mod); + } + + public Object getMod() + { + return wrappedContainer.getMod(); + } + + public ArtifactVersion getProcessedVersion() + { + return wrappedContainer.getProcessedVersion(); + } + + @Override + public boolean isNetworkMod() + { + return wrappedContainer.isNetworkMod(); + } + @Override + public boolean isImmutable() + { + return true; + } + + @Override + public String getDisplayVersion() + { + return wrappedContainer.getDisplayVersion(); + } + + @Override + public VersionRange acceptableMinecraftVersionRange() + { + return wrappedContainer.acceptableMinecraftVersionRange(); + } + + public WorldAccessContainer getWrappedWorldAccessContainer() + { + if (wrappedContainer instanceof WorldAccessContainer) + { + return (WorldAccessContainer) wrappedContainer; + } + else + { + return null; + } + } + + @Override + public Certificate getSigningCertificate() + { + return wrappedContainer.getSigningCertificate(); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/LoadController.java b/src/minecraft/cpw/mods/fml/common/LoadController.java new file mode 100644 index 0000000..123e996 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/LoadController.java @@ -0,0 +1,240 @@ +package cpw.mods.fml.common; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; + +import com.google.common.base.Joiner; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import cpw.mods.fml.common.LoaderState.ModState; +import cpw.mods.fml.common.event.FMLEvent; +import cpw.mods.fml.common.event.FMLLoadEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLStateEvent; + +public class LoadController +{ + private Loader loader; + private EventBus masterChannel; + private ImmutableMap eventChannels; + private LoaderState state; + private Multimap modStates = ArrayListMultimap.create(); + private Multimap errors = ArrayListMultimap.create(); + private Map modList; + private List activeModList = Lists.newArrayList(); + private ModContainer activeContainer; + private BiMap modObjectList; + + public LoadController(Loader loader) + { + this.loader = loader; + this.masterChannel = new EventBus("FMLMainChannel"); + this.masterChannel.register(this); + + state = LoaderState.NOINIT; + + + } + + @Subscribe + public void buildModList(FMLLoadEvent event) + { + this.modList = loader.getIndexedModList(); + Builder eventBus = ImmutableMap.builder(); + + for (ModContainer mod : loader.getModList()) + { + EventBus bus = new EventBus(mod.getModId()); + boolean isActive = mod.registerBus(bus, this); + if (isActive) + { + FMLLog.fine("Activating mod %s", mod.getModId()); + activeModList.add(mod); + modStates.put(mod.getModId(), ModState.UNLOADED); + eventBus.put(mod.getModId(), bus); + } + else + { + FMLLog.warning("Mod %s has been disabled through configuration", mod.getModId()); + modStates.put(mod.getModId(), ModState.UNLOADED); + modStates.put(mod.getModId(), ModState.DISABLED); + } + } + + eventChannels = eventBus.build(); + } + + public void distributeStateMessage(LoaderState state, Object... eventData) + { + if (state.hasEvent()) + { + masterChannel.post(state.getEvent(eventData)); + } + } + + public void transition(LoaderState desiredState) + { + LoaderState oldState = state; + state = state.transition(!errors.isEmpty()); + if (state != desiredState) + { + Throwable toThrow = null; + FMLLog.severe("Fatal errors were detected during the transition from %s to %s. Loading cannot continue", oldState, desiredState); + StringBuilder sb = new StringBuilder(); + printModStates(sb); + FMLLog.getLogger().severe(sb.toString()); + FMLLog.severe("The following problems were captured during this phase"); + for (Entry error : errors.entries()) + { + FMLLog.log(Level.SEVERE, error.getValue(), "Caught exception from %s", error.getKey()); + if (error.getValue() instanceof IFMLHandledException) + { + toThrow = error.getValue(); + } + else if (toThrow == null) + { + toThrow = error.getValue(); + } + } + if (toThrow != null && toThrow instanceof RuntimeException) + { + throw (RuntimeException)toThrow; + } + else + { + throw new LoaderException(toThrow); + } + } + } + + public ModContainer activeContainer() + { + return activeContainer; + } + + @Subscribe + public void propogateStateMessage(FMLEvent stateEvent) + { + if (stateEvent instanceof FMLPreInitializationEvent) + { + modObjectList = buildModObjectList(); + } + for (ModContainer mc : activeModList) + { + activeContainer = mc; + String modId = mc.getModId(); + stateEvent.applyModContainer(activeContainer()); + FMLLog.finer("Sending event %s to mod %s", stateEvent.getEventType(), modId); + eventChannels.get(modId).post(stateEvent); + FMLLog.finer("Sent event %s to mod %s", stateEvent.getEventType(), modId); + activeContainer = null; + if (stateEvent instanceof FMLStateEvent) + { + if (!errors.containsKey(modId)) + { + modStates.put(modId, ((FMLStateEvent)stateEvent).getModState()); + } + else + { + modStates.put(modId, ModState.ERRORED); + } + } + } + } + + public ImmutableBiMap buildModObjectList() + { + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + for (ModContainer mc : activeModList) + { + if (!mc.isImmutable() && mc.getMod()!=null) + { + builder.put(mc, mc.getMod()); + } + if (mc.getMod()==null && !mc.isImmutable() && state!=LoaderState.CONSTRUCTING) + { + FMLLog.severe("There is a severe problem with %s - it appears not to have constructed correctly", mc.getModId()); + if (state != LoaderState.CONSTRUCTING) + { + this.errorOccurred(mc, new RuntimeException()); + } + } + } + return builder.build(); + } + + public void errorOccurred(ModContainer modContainer, Throwable exception) + { + if (exception instanceof InvocationTargetException) + { + errors.put(modContainer.getModId(), ((InvocationTargetException)exception).getCause()); + } + else + { + errors.put(modContainer.getModId(), exception); + } + } + + public void printModStates(StringBuilder ret) + { + for (ModContainer mc : loader.getModList()) + { + ret.append("\n\t").append(mc.getModId()).append(" [").append(mc.getName()).append("] (").append(mc.getSource().getName()).append(") "); + Joiner.on("->"). appendTo(ret, modStates.get(mc.getModId())); + } + } + + public List getActiveModList() + { + return activeModList; + } + + public ModState getModState(ModContainer selectedMod) + { + return Iterables.getLast(modStates.get(selectedMod.getModId()), ModState.AVAILABLE); + } + + public void distributeStateMessage(Class customEvent) + { + try + { + masterChannel.post(customEvent.newInstance()); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "An unexpected exception"); + throw new LoaderException(e); + } + } + + public BiMap getModObjectList() + { + if (modObjectList == null) + { + FMLLog.severe("Detected an attempt by a mod %s to perform game activity during mod construction. This is a serious programming error.", activeContainer); + return buildModObjectList(); + } + return ImmutableBiMap.copyOf(modObjectList); + } + + public boolean isInState(LoaderState state) + { + return this.state == state; + } + + boolean hasReachedState(LoaderState state) { + return this.state.ordinal()>=state.ordinal() && this.state!=LoaderState.ERRORED; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/Loader.java b/src/minecraft/cpw/mods/fml/common/Loader.java new file mode 100644 index 0000000..d66ec02 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/Loader.java @@ -0,0 +1,755 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.logging.Level; + +import net.minecraft.crash.CallableMinecraftVersion; +import net.minecraft.server.MinecraftServer; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; +import com.google.common.collect.Multiset.Entry; +import com.google.common.collect.Multisets; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets.SetView; +import com.google.common.collect.TreeMultimap; + +import cpw.mods.fml.common.LoaderState.ModState; +import cpw.mods.fml.common.discovery.ModDiscoverer; +import cpw.mods.fml.common.event.FMLInterModComms; +import cpw.mods.fml.common.event.FMLLoadEvent; +import cpw.mods.fml.common.functions.ModIdFunction; +import cpw.mods.fml.common.modloader.BaseModProxy; +import cpw.mods.fml.common.toposort.ModSorter; +import cpw.mods.fml.common.toposort.ModSortingException; +import cpw.mods.fml.common.toposort.TopologicalSort; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; + +/** + * The loader class performs the actual loading of the mod code from disk. + * + *

+ * There are several {@link LoaderState}s to mod loading, triggered in two + * different stages from the FML handler code's hooks into the minecraft code. + *

+ * + *
    + *
  1. LOADING. Scanning the filesystem for mod containers to load (zips, jars, + * directories), adding them to the {@link #modClassLoader} Scanning, the loaded + * containers for mod classes to load and registering them appropriately.
  2. + *
  3. PREINIT. The mod classes are configured, they are sorted into a load + * order, and instances of the mods are constructed.
  4. + *
  5. INIT. The mod instances are initialized. For BaseMod mods, this involves + * calling the load method.
  6. + *
  7. POSTINIT. The mod instances are post initialized. For BaseMod mods this + * involves calling the modsLoaded method.
  8. + *
  9. UP. The Loader is complete
  10. + *
  11. ERRORED. The loader encountered an error during the LOADING phase and + * dropped to this state instead. It will not complete loading from this state, + * but it attempts to continue loading before abandoning and giving a fatal + * error.
  12. + *
+ * + * Phase 1 code triggers the LOADING and PREINIT states. Phase 2 code triggers + * the INIT and POSTINIT states. + * + * @author cpw + * + */ +public class Loader +{ + private static final Splitter DEPENDENCYPARTSPLITTER = Splitter.on(":").omitEmptyStrings().trimResults(); + private static final Splitter DEPENDENCYSPLITTER = Splitter.on(";").omitEmptyStrings().trimResults(); + /** + * The singleton instance + */ + private static Loader instance; + /** + * Build information for tracking purposes. + */ + private static String major; + private static String minor; + private static String rev; + private static String build; + private static String mccversion; + private static String mcpversion; + + /** + * The class loader we load the mods into. + */ + private ModClassLoader modClassLoader; + /** + * The sorted list of mods. + */ + private List mods; + /** + * A named list of mods + */ + private Map namedMods; + /** + * The canonical configuration directory + */ + private File canonicalConfigDir; + /** + * The canonical minecraft directory + */ + private File canonicalMinecraftDir; + /** + * The captured error + */ + private Exception capturedError; + private File canonicalModsDir; + private LoadController modController; + private MinecraftDummyContainer minecraft; + private MCPDummyContainer mcp; + + private static File minecraftDir; + private static List injectedContainers; + + public static Loader instance() + { + if (instance == null) + { + instance = new Loader(); + } + + return instance; + } + + public static void injectData(Object... data) + { + major = (String) data[0]; + minor = (String) data[1]; + rev = (String) data[2]; + build = (String) data[3]; + mccversion = (String) data[4]; + mcpversion = (String) data[5]; + minecraftDir = (File) data[6]; + injectedContainers = (List)data[7]; + } + + private Loader() + { + modClassLoader = new ModClassLoader(getClass().getClassLoader()); + String actualMCVersion = new CallableMinecraftVersion(null).minecraftVersion(); + if (!mccversion.equals(actualMCVersion)) + { + FMLLog.severe("This version of FML is built for Minecraft %s, we have detected Minecraft %s in your minecraft jar file", mccversion, actualMCVersion); + throw new LoaderException(); + } + + minecraft = new MinecraftDummyContainer(actualMCVersion); + mcp = new MCPDummyContainer(MetadataCollection.from(getClass().getResourceAsStream("/mcpmod.info"), "MCP").getMetadataForId("mcp", null)); + } + + /** + * Sort the mods into a sorted list, using dependency information from the + * containers. The sorting is performed using a {@link TopologicalSort} + * based on the pre- and post- dependency information provided by the mods. + */ + private void sortModList() + { + FMLLog.fine("Verifying mod requirements are satisfied"); + try + { + BiMap modVersions = HashBiMap.create(); + for (ModContainer mod : getActiveModList()) + { + modVersions.put(mod.getModId(), mod.getProcessedVersion()); + } + + for (ModContainer mod : getActiveModList()) + { + if (!mod.acceptableMinecraftVersionRange().containsVersion(minecraft.getProcessedVersion())) + { + FMLLog.severe("The mod %s does not wish to run in Minecraft version %s. You will have to remove it to play.", mod.getModId(), getMCVersionString()); + throw new WrongMinecraftVersionException(mod); + } + Map names = Maps.uniqueIndex(mod.getRequirements(), new Function() + { + public String apply(ArtifactVersion v) + { + return v.getLabel(); + } + }); + Set versionMissingMods = Sets.newHashSet(); + Set missingMods = Sets.difference(names.keySet(), modVersions.keySet()); + if (!missingMods.isEmpty()) + { + FMLLog.severe("The mod %s (%s) requires mods %s to be available", mod.getModId(), mod.getName(), missingMods); + for (String modid : missingMods) + { + versionMissingMods.add(names.get(modid)); + } + throw new MissingModsException(versionMissingMods); + } + ImmutableList allDeps = ImmutableList.builder().addAll(mod.getDependants()).addAll(mod.getDependencies()).build(); + for (ArtifactVersion v : allDeps) + { + if (modVersions.containsKey(v.getLabel())) + { + if (!v.containsVersion(modVersions.get(v.getLabel()))) + { + versionMissingMods.add(v); + } + } + } + if (!versionMissingMods.isEmpty()) + { + FMLLog.severe("The mod %s (%s) requires mod versions %s to be available", mod.getModId(), mod.getName(), versionMissingMods); + throw new MissingModsException(versionMissingMods); + } + } + + FMLLog.fine("All mod requirements are satisfied"); + + ModSorter sorter = new ModSorter(getActiveModList(), namedMods); + + try + { + FMLLog.fine("Sorting mods into an ordered list"); + List sortedMods = sorter.sort(); + // Reset active list to the sorted list + modController.getActiveModList().clear(); + modController.getActiveModList().addAll(sortedMods); + // And inject the sorted list into the overall list + mods.removeAll(sortedMods); + sortedMods.addAll(mods); + mods = sortedMods; + FMLLog.fine("Mod sorting completed successfully"); + } + catch (ModSortingException sortException) + { + FMLLog.severe("A dependency cycle was detected in the input mod set so an ordering cannot be determined"); + FMLLog.severe("The visited mod list is %s", sortException.getExceptionData().getVisitedNodes()); + FMLLog.severe("The first mod in the cycle is %s", sortException.getExceptionData().getFirstBadNode()); + FMLLog.log(Level.SEVERE, sortException, "The full error"); + throw new LoaderException(sortException); + } + } + finally + { + FMLLog.fine("Mod sorting data:"); + for (ModContainer mod : getActiveModList()) + { + if (!mod.isImmutable()) + { + FMLLog.fine("\t%s(%s:%s): %s (%s)", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName(), mod.getSortingRules()); + } + } + if (mods.size()==0) + { + FMLLog.fine("No mods found to sort"); + } + } + + } + + /** + * The primary loading code + * + * This is visited during first initialization by Minecraft to scan and load + * the mods from all sources 1. The minecraft jar itself (for loading of in + * jar mods- I would like to remove this if possible but forge depends on it + * at present) 2. The mods directory with expanded subdirs, searching for + * mods named mod_*.class 3. The mods directory for zip and jar files, + * searching for mod classes named mod_*.class again + * + * The found resources are first loaded into the {@link #modClassLoader} + * (always) then scanned for class resources matching the specification + * above. + * + * If they provide the {@link Mod} annotation, they will be loaded as + * "FML mods", which currently is effectively a NO-OP. If they are + * determined to be {@link BaseModProxy} subclasses they are loaded as such. + * + * Finally, if they are successfully loaded as classes, they are then added + * to the available mod list. + */ + private ModDiscoverer identifyMods() + { + FMLLog.fine("Building injected Mod Containers %s", injectedContainers); + // Add in the MCP mod container + mods.add(new InjectedModContainer(mcp,new File("minecraft.jar"))); + File coremod = new File(minecraftDir,"coremods"); + for (String cont : injectedContainers) + { + ModContainer mc; + try + { + mc = (ModContainer) Class.forName(cont,true,modClassLoader).newInstance(); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "A problem occured instantiating the injected mod container %s", cont); + throw new LoaderException(e); + } + mods.add(new InjectedModContainer(mc,coremod)); + } + ModDiscoverer discoverer = new ModDiscoverer(); + FMLLog.fine("Attempting to load mods contained in the minecraft jar file and associated classes"); + discoverer.findClasspathMods(modClassLoader); + FMLLog.fine("Minecraft jar mods loaded successfully"); + + FMLLog.info("Searching %s for mods", canonicalModsDir.getAbsolutePath()); + discoverer.findModDirMods(canonicalModsDir); + + mods.addAll(discoverer.identifyMods()); + identifyDuplicates(mods); + namedMods = Maps.uniqueIndex(mods, new ModIdFunction()); + FMLLog.info("Forge Mod Loader has identified %d mod%s to load", mods.size(), mods.size() != 1 ? "s" : ""); + return discoverer; + } + + private class ModIdComparator implements Comparator + { + @Override + public int compare(ModContainer o1, ModContainer o2) + { + return o1.getModId().compareTo(o2.getModId()); + } + + } + + private void identifyDuplicates(List mods) + { + TreeMultimap dupsearch = TreeMultimap.create(new ModIdComparator(), Ordering.arbitrary()); + for (ModContainer mc : mods) + { + if (mc.getSource() != null) + { + dupsearch.put(mc, mc.getSource()); + } + } + + ImmutableMultiset duplist = Multisets.copyHighestCountFirst(dupsearch.keys()); + SetMultimap dupes = LinkedHashMultimap.create(); + for (Entry e : duplist.entrySet()) + { + if (e.getCount() > 1) + { + FMLLog.severe("Found a duplicate mod %s at %s", e.getElement().getModId(), dupsearch.get(e.getElement())); + dupes.putAll(e.getElement(),dupsearch.get(e.getElement())); + } + } + if (!dupes.isEmpty()) + { + throw new DuplicateModsFoundException(dupes); + } + } + + /** + * @return + */ + private void initializeLoader() + { + File modsDir = new File(minecraftDir, "mods"); + File configDir = new File(minecraftDir, "config"); + String canonicalModsPath; + String canonicalConfigPath; + + try + { + canonicalMinecraftDir = minecraftDir.getCanonicalFile(); + canonicalModsPath = modsDir.getCanonicalPath(); + canonicalConfigPath = configDir.getCanonicalPath(); + canonicalConfigDir = configDir.getCanonicalFile(); + canonicalModsDir = modsDir.getCanonicalFile(); + } + catch (IOException ioe) + { + FMLLog.log(Level.SEVERE, ioe, "Failed to resolve loader directories: mods : %s ; config %s", canonicalModsDir.getAbsolutePath(), + configDir.getAbsolutePath()); + throw new LoaderException(ioe); + } + + if (!canonicalModsDir.exists()) + { + FMLLog.info("No mod directory found, creating one: %s", canonicalModsPath); + boolean dirMade = canonicalModsDir.mkdir(); + if (!dirMade) + { + FMLLog.severe("Unable to create the mod directory %s", canonicalModsPath); + throw new LoaderException(); + } + FMLLog.info("Mod directory created successfully"); + } + + if (!canonicalConfigDir.exists()) + { + FMLLog.fine("No config directory found, creating one: %s", canonicalConfigPath); + boolean dirMade = canonicalConfigDir.mkdir(); + if (!dirMade) + { + FMLLog.severe("Unable to create the config directory %s", canonicalConfigPath); + throw new LoaderException(); + } + FMLLog.info("Config directory created successfully"); + } + + if (!canonicalModsDir.isDirectory()) + { + FMLLog.severe("Attempting to load mods from %s, which is not a directory", canonicalModsPath); + throw new LoaderException(); + } + + if (!configDir.isDirectory()) + { + FMLLog.severe("Attempting to load configuration from %s, which is not a directory", canonicalConfigPath); + throw new LoaderException(); + } + } + + public List getModList() + { + return instance().mods != null ? ImmutableList.copyOf(instance().mods) : ImmutableList.of(); + } + + /** + * Called from the hook to start mod loading. We trigger the + * {@link #identifyMods()} and Constructing, Preinitalization, and Initalization phases here. Finally, + * the mod list is frozen completely and is consider immutable from then on. + */ + public void loadMods() + { + initializeLoader(); + mods = Lists.newArrayList(); + namedMods = Maps.newHashMap(); + modController = new LoadController(this); + modController.transition(LoaderState.LOADING); + ModDiscoverer disc = identifyMods(); + disableRequestedMods(); + modController.distributeStateMessage(FMLLoadEvent.class); + sortModList(); + mods = ImmutableList.copyOf(mods); + for (File nonMod : disc.getNonModLibs()) + { + if (nonMod.isFile()) + { + FMLLog.info("FML has found a non-mod file %s in your mods directory. It will now be injected into your classpath. This could severe stability issues, it should be removed if possible.", nonMod.getName()); + try + { + modClassLoader.addFile(nonMod); + } + catch (MalformedURLException e) + { + FMLLog.log(Level.SEVERE, e, "Encountered a weird problem with non-mod file injection : %s", nonMod.getName()); + } + } + } + modController.transition(LoaderState.CONSTRUCTING); + modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable()); + FMLLog.fine("Mod signature data:"); + for (ModContainer mod : getActiveModList()) + { + FMLLog.fine("\t%s(%s:%s): %s (%s)", mod.getModId(), mod.getName(), mod.getVersion(), mod.getSource().getName(), CertificateHelper.getFingerprint(mod.getSigningCertificate())); + } + modController.transition(LoaderState.PREINITIALIZATION); + modController.distributeStateMessage(LoaderState.PREINITIALIZATION, disc.getASMTable(), canonicalConfigDir); + modController.transition(LoaderState.INITIALIZATION); + } + + private void disableRequestedMods() + { + String forcedModList = System.getProperty("fml.modStates", ""); + FMLLog.fine("Received a system property request \'%s\'",forcedModList); + Map sysPropertyStateList = Splitter.on(CharMatcher.anyOf(";:")) + .omitEmptyStrings().trimResults().withKeyValueSeparator("=") + .split(forcedModList); + FMLLog.fine("System property request managing the state of %d mods", sysPropertyStateList.size()); + Map modStates = Maps.newHashMap(); + + File forcedModFile = new File(canonicalConfigDir, "fmlModState.properties"); + Properties forcedModListProperties = new Properties(); + if (forcedModFile.exists() && forcedModFile.isFile()) + { + FMLLog.fine("Found a mod state file %s", forcedModFile.getName()); + try + { + forcedModListProperties.load(new FileReader(forcedModFile)); + FMLLog.fine("Loaded states for %d mods from file", forcedModListProperties.size()); + } + catch (Exception e) + { + FMLLog.log(Level.INFO, e, "An error occurred reading the fmlModState.properties file"); + } + } + modStates.putAll(Maps.fromProperties(forcedModListProperties)); + modStates.putAll(sysPropertyStateList); + FMLLog.fine("After merging, found state information for %d mods", modStates.size()); + + Map isEnabled = Maps.transformValues(modStates, new Function() + { + public Boolean apply(String input) + { + return Boolean.parseBoolean(input); + } + }); + + for (Map.Entry entry : isEnabled.entrySet()) + { + if (namedMods.containsKey(entry.getKey())) + { + FMLLog.info("Setting mod %s to enabled state %b", entry.getKey(), entry.getValue()); + namedMods.get(entry.getKey()).setEnabledState(entry.getValue()); + } + } + } + + /** + * Query if we know of a mod named modname + * + * @param modname + * @return If the mod is loaded + */ + public static boolean isModLoaded(String modname) + { + return instance().namedMods.containsKey(modname) && instance().modController.getModState(instance.namedMods.get(modname))!=ModState.DISABLED; + } + + public File getConfigDir() + { + return canonicalConfigDir; + } + + public String getCrashInformation() + { + StringBuilder ret = new StringBuilder(); + List branding = FMLCommonHandler.instance().getBrandings(); + + Joiner.on(' ').skipNulls().appendTo(ret, branding.subList(1, branding.size())); + if (modController!=null) + { + modController.printModStates(ret); + } + return ret.toString(); + } + + public String getFMLVersionString() + { + return String.format("%s.%s.%s.%s", major, minor, rev, build); + } + + public ClassLoader getModClassLoader() + { + return modClassLoader; + } + + public void computeDependencies(String dependencyString, Set requirements, List dependencies, List dependants) + { + if (dependencyString == null || dependencyString.length() == 0) + { + return; + } + + boolean parseFailure=false; + + for (String dep : DEPENDENCYSPLITTER.split(dependencyString)) + { + List depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep)); + // Need two parts to the string + if (depparts.size() != 2) + { + parseFailure=true; + continue; + } + String instruction = depparts.get(0); + String target = depparts.get(1); + boolean targetIsAll = target.startsWith("*"); + + // Cannot have an "all" relationship with anything except pure * + if (targetIsAll && target.length()>1) + { + parseFailure = true; + continue; + } + + // If this is a required element, add it to the required list + if ("required-before".equals(instruction) || "required-after".equals(instruction)) + { + // You can't require everything + if (!targetIsAll) + { + requirements.add(VersionParser.parseVersionReference(target)); + } + else + { + parseFailure=true; + continue; + } + } + + // You cannot have a versioned dependency on everything + if (targetIsAll && target.indexOf('@')>-1) + { + parseFailure = true; + continue; + } + // before elements are things we are loaded before (so they are our dependants) + if ("required-before".equals(instruction) || "before".equals(instruction)) + { + dependants.add(VersionParser.parseVersionReference(target)); + } + // after elements are things that load before we do (so they are out dependencies) + else if ("required-after".equals(instruction) || "after".equals(instruction)) + { + dependencies.add(VersionParser.parseVersionReference(target)); + } + else + { + parseFailure=true; + } + } + + if (parseFailure) + { + FMLLog.log(Level.WARNING, "Unable to parse dependency string %s", dependencyString); + throw new LoaderException(); + } + } + + public Map getIndexedModList() + { + return ImmutableMap.copyOf(namedMods); + } + + public void initializeMods() + { + // Mod controller should be in the initialization state here + modController.distributeStateMessage(LoaderState.INITIALIZATION); + modController.transition(LoaderState.POSTINITIALIZATION); + modController.distributeStateMessage(FMLInterModComms.IMCEvent.class); + modController.distributeStateMessage(LoaderState.POSTINITIALIZATION); + modController.transition(LoaderState.AVAILABLE); + modController.distributeStateMessage(LoaderState.AVAILABLE); + FMLLog.info("Forge Mod Loader has successfully loaded %d mod%s", mods.size(), mods.size()==1 ? "" : "s"); + } + + public ICrashCallable getCallableCrashInformation() + { + return new ICrashCallable() { + @Override + public String call() throws Exception + { + return getCrashInformation(); + } + + @Override + public String getLabel() + { + return "FML"; + } + }; + } + + public List getActiveModList() + { + return modController != null ? modController.getActiveModList() : ImmutableList.of(); + } + + public ModState getModState(ModContainer selectedMod) + { + return modController.getModState(selectedMod); + } + + public String getMCVersionString() + { + return "Minecraft " + mccversion; + } + + public void serverStarting(Object server) + { + modController.distributeStateMessage(LoaderState.SERVER_STARTING, server); + modController.transition(LoaderState.SERVER_STARTING); + } + + public void serverStarted() + { + modController.distributeStateMessage(LoaderState.SERVER_STARTED); + modController.transition(LoaderState.SERVER_STARTED); + } + + public void serverStopping() + { + modController.distributeStateMessage(LoaderState.SERVER_STOPPING); + modController.transition(LoaderState.SERVER_STOPPING); + } + + public BiMap getModObjectList() + { + return modController.getModObjectList(); + } + + public BiMap getReversedModObjectList() + { + return getModObjectList().inverse(); + } + + public ModContainer activeModContainer() + { + return modController != null ? modController.activeContainer() : null; + } + + public boolean isInState(LoaderState state) + { + return modController.isInState(state); + } + + public MinecraftDummyContainer getMinecraftModContainer() + { + return minecraft; + } + + public boolean hasReachedState(LoaderState state) { + return modController != null ? modController.hasReachedState(state) : false; + } + + public String getMCPVersionString() { + return String.format("MCP v%s", mcpversion); + } + + public void serverStopped() + { + modController.distributeStateMessage(LoaderState.SERVER_STOPPED); + modController.transition(LoaderState.SERVER_STOPPED); + modController.transition(LoaderState.AVAILABLE); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/LoaderException.java b/src/minecraft/cpw/mods/fml/common/LoaderException.java new file mode 100644 index 0000000..29f5c66 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/LoaderException.java @@ -0,0 +1,31 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +public class LoaderException extends RuntimeException +{ + /** + * + */ + private static final long serialVersionUID = -5675297950958861378L; + + public LoaderException(Throwable wrapped) + { + super(wrapped); + } + + public LoaderException() + { + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/LoaderState.java b/src/minecraft/cpw/mods/fml/common/LoaderState.java new file mode 100644 index 0000000..0bd7803 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/LoaderState.java @@ -0,0 +1,106 @@ +package cpw.mods.fml.common; + +import com.google.common.base.Throwables; + +import cpw.mods.fml.common.event.FMLConstructionEvent; +import cpw.mods.fml.common.event.FMLEvent; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLLoadCompleteEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStartedEvent; +import cpw.mods.fml.common.event.FMLServerStartingEvent; +import cpw.mods.fml.common.event.FMLServerStoppedEvent; +import cpw.mods.fml.common.event.FMLServerStoppingEvent; +import cpw.mods.fml.common.event.FMLStateEvent; + +/** + * The state enum used to help track state progression for the loader + * @author cpw + * + */ +public enum LoaderState +{ + NOINIT("Uninitialized",null), + LOADING("Loading",null), + CONSTRUCTING("Constructing mods",FMLConstructionEvent.class), + PREINITIALIZATION("Pre-initializing mods", FMLPreInitializationEvent.class), + INITIALIZATION("Initializing mods", FMLInitializationEvent.class), + POSTINITIALIZATION("Post-initializing mods", FMLPostInitializationEvent.class), + AVAILABLE("Mod loading complete", FMLLoadCompleteEvent.class), + SERVER_STARTING("Server starting", FMLServerStartingEvent.class), + SERVER_STARTED("Server started", FMLServerStartedEvent.class), + SERVER_STOPPING("Server stopping", FMLServerStoppingEvent.class), + SERVER_STOPPED("Server stopped", FMLServerStoppedEvent.class), + ERRORED("Mod Loading errored",null); + + + private Class eventClass; + private String name; + + private LoaderState(String name, Class event) + { + this.name = name; + this.eventClass = event; + } + + public LoaderState transition(boolean errored) + { + if (errored) + { + return ERRORED; + } + // stopping -> available + if (this == SERVER_STOPPED) + { + return AVAILABLE; + } + return values()[ordinal() < values().length ? ordinal()+1 : ordinal()]; + } + + public boolean hasEvent() + { + return eventClass != null; + } + + public FMLStateEvent getEvent(Object... eventData) + { + try + { + return eventClass.getConstructor(Object[].class).newInstance((Object)eventData); + } + catch (Exception e) + { + throw Throwables.propagate(e); + } + } + public LoaderState requiredState() + { + if (this == NOINIT) return NOINIT; + return LoaderState.values()[this.ordinal()-1]; + } + public enum ModState + { + UNLOADED("Unloaded"), + LOADED("Loaded"), + CONSTRUCTED("Constructed"), + PREINITIALIZED("Pre-initialized"), + INITIALIZED("Initialized"), + POSTINITIALIZED("Post-initialized"), + AVAILABLE("Available"), + DISABLED("Disabled"), + ERRORED("Errored"); + + private String label; + + private ModState(String label) + { + this.label = label; + } + + public String toString() + { + return this.label; + } + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/MCPDummyContainer.java b/src/minecraft/cpw/mods/fml/common/MCPDummyContainer.java new file mode 100644 index 0000000..6ac0670 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/MCPDummyContainer.java @@ -0,0 +1,14 @@ +package cpw.mods.fml.common; + +import com.google.common.eventbus.EventBus; + +public class MCPDummyContainer extends DummyModContainer { + public MCPDummyContainer(ModMetadata metadata) { + super(metadata); + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + return true; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/MetadataCollection.java b/src/minecraft/cpw/mods/fml/common/MetadataCollection.java new file mode 100644 index 0000000..0eef493 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/MetadataCollection.java @@ -0,0 +1,91 @@ +package cpw.mods.fml.common; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.logging.Level; + +import argo.jdom.JdomParser; +import argo.jdom.JsonNode; +import argo.jdom.JsonRootNode; +import argo.saj.InvalidSyntaxException; + +import com.google.common.base.Throwables; +import com.google.common.collect.Maps; + +public class MetadataCollection +{ + private static JdomParser parser = new JdomParser(); + private Map metadatas = Maps.newHashMap(); + private int metadataVersion = 1; + + public static MetadataCollection from(InputStream inputStream, String sourceName) + { + if (inputStream == null) + { + return new MetadataCollection(); + } + + InputStreamReader reader = new InputStreamReader(inputStream); + try + { + JsonRootNode root = parser.parse(reader); + if (root.hasElements()) + { + return parse10ModInfo(root); + } + else + { + return parseModInfo(root); + } + } + catch (InvalidSyntaxException e) + { + FMLLog.log(Level.SEVERE, e, "The mcmod.info file in %s cannot be parsed as valid JSON. It will be ignored", sourceName); + return new MetadataCollection(); + } + catch (Exception e) + { + throw Throwables.propagate(e); + } + } + + private static MetadataCollection parseModInfo(JsonRootNode root) + { + MetadataCollection mc = new MetadataCollection(); + mc.metadataVersion = Integer.parseInt(root.getNumberValue("modinfoversion")); + mc.parseModMetadataList(root.getNode("modlist")); + return mc; + } + + private static MetadataCollection parse10ModInfo(JsonRootNode root) + { + MetadataCollection mc = new MetadataCollection(); + mc.parseModMetadataList(root); + return mc; + } + + private void parseModMetadataList(JsonNode metadataList) + { + for (JsonNode node : metadataList.getElements()) + { + ModMetadata mmd = new ModMetadata(node); + metadatas.put(mmd.modId, mmd); + } + } + + public ModMetadata getMetadataForId(String modId, Map extraData) + { + if (!metadatas.containsKey(modId)) + { + ModMetadata dummy = new ModMetadata(); + dummy.modId = modId; + dummy.name = (String) extraData.get("name"); + dummy.version = (String) extraData.get("version"); + dummy.autogenerated = true; + metadatas.put(modId, dummy); + } + return metadatas.get(modId); + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/MinecraftDummyContainer.java b/src/minecraft/cpw/mods/fml/common/MinecraftDummyContainer.java new file mode 100644 index 0000000..fec8d03 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/MinecraftDummyContainer.java @@ -0,0 +1,26 @@ +package cpw.mods.fml.common; + +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.common.versioning.VersionRange; + +public class MinecraftDummyContainer extends DummyModContainer +{ + + private VersionRange staticRange; + public MinecraftDummyContainer(String actualMCVersion) + { + super(new ModMetadata()); + getMetadata().modId = "Minecraft"; + getMetadata().name = "Minecraft"; + getMetadata().version = actualMCVersion; + staticRange = VersionParser.parseRange("["+actualMCVersion+"]"); + } + + + public VersionRange getStaticVersionRange() + { + return staticRange; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/MissingModsException.java b/src/minecraft/cpw/mods/fml/common/MissingModsException.java new file mode 100644 index 0000000..b0d0cd2 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/MissingModsException.java @@ -0,0 +1,18 @@ +package cpw.mods.fml.common; + +import java.util.Set; + +import com.google.common.collect.Sets.SetView; + +import cpw.mods.fml.common.versioning.ArtifactVersion; + +public class MissingModsException extends RuntimeException +{ + + public Set missingMods; + + public MissingModsException(Set missingMods) + { + this.missingMods = missingMods; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/Mod.java b/src/minecraft/cpw/mods/fml/common/Mod.java new file mode 100644 index 0000000..5aa5cd4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/Mod.java @@ -0,0 +1,248 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.minecraft.item.ItemBlock; + +import cpw.mods.fml.common.event.FMLInterModComms; +import cpw.mods.fml.common.event.FMLInterModComms.IMCMessage; + +/** + * The new mod style in FML 1.3 + * + * @author cpw + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Mod +{ + /** + * The unique mod identifier for this mod + */ + String modid(); + /** + * A user friendly name for the mod + */ + String name() default ""; + /** + * A version string for this mod + */ + String version() default ""; + /** + * A simple dependency string for this mod (see modloader's "priorities" string specification) + */ + String dependencies() default ""; + /** + * Whether to use the mcmod.info metadata by default for this mod. + * If true, settings in the mcmod.info file will override settings in these annotations. + */ + boolean useMetadata() default false; + + /** + * The acceptable range of minecraft versions that this mod will load and run in + * The default ("empty string") indicates that only the current minecraft version is acceptable. + * FML will refuse to run with an error if the minecraft version is not in this range across all mods. + * @return A version range as specified by the maven version range specification or the empty string + */ + String acceptedMinecraftVersions() default ""; + /** + * An optional bukkit plugin that will be injected into the bukkit plugin framework if + * this mod is loaded into the FML framework and the bukkit coremod is present. + * Instances of the bukkit plugin can be obtained via the {@link BukkitPluginRef} annotation on fields. + * @return The name of the plugin to load for this mod + */ + String bukkitPlugin() default ""; + /** + * Mods that this mod will not load with. + * An optional comma separated string of (+|-)(*|modid[@value]) which specify mods that + * this mod will refuse to load with, resulting in the game failing to start. + * Entries can be prefixed with a + for a positive exclusion assertion, or - for a negative exclusion + * assertion. Asterisk is the wildcard and represents all mods. + * + * The only mods that cannot be excluded are FML and MCP, trivially. + * Other special values: + *
    + *
  • +f indicates that the mod will accept a minecraft forge environment.
  • + *
  • -* indicates that the mod will not accept any other mods.
  • + *
+ * + * Some examples: + *
    + *
  • -*,+f,+IronChest: Will run only in a minecraft forge environment with the mod IronChests. + * The -* forces all mods to be excluded, then the +f and +IronChest add into the "allowed list".
  • + *
  • +f,-IC2: Will run in a minecraft forge environment but will not run if + * IndustrialCraft 2 (IC2) is loaded alongside.
  • + *
  • -*: Will not run if any othe mod is loaded except MCP/FML itself.
  • + *
+ * + * If a mod is present on the excluded list, the game will stop and show an error screen. If the + * class containing the {@link Mod} annotation has a "getCustomErrorException" method, it will be + * called to retrieve a custom error message for display in this case. If two mods have a declared + * exclusion which is matched, the screen that is shown is indeterminate. + * + * @return A string listing modids to exclude from loading with this mod. + */ + String modExclusionList() default ""; + /** + * Specifying this field allows for a mod to expect a signed jar with a fingerprint matching this value. + * The fingerprint should be SHA-1 encoded, lowercase with ':' removed. An empty value indicates that + * the mod is not expecting to be signed. + * + * Any incorrectness of the fingerprint, be it missing or wrong, will result in the {@link FingerprintWarning} + * method firing prior to any other event on the mod. + * + * @return A certificate fingerprint that is expected for this mod. + */ + String certificateFingerprint() default ""; + /** + * Mark the designated method as to be called at if there is something wrong with the certificate fingerprint of + * the mod's jar, or it is missing, or otherwise a problem. + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface FingerprintWarning {} + /** + * Mark the designated method as being called at the "pre-initialization" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface PreInit {} + /** + * Mark the designated method as being called at the "initialization" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface Init {} + /** + * Mark the designated method as being called at the "post-initialization" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface PostInit {} + /** + * Mark the designated method as being called at the "server-starting" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ServerStarting {} + /** + * Mark the designated method as being called at the "server-started" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ServerStarted {} + /** + * Mark the designated method as being called at the "server-stopping" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ServerStopping {} + /** + * Mark the designated method as being called at the "server-stopped" phase + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ServerStopped {} + /** + * Mark the designated method as the receiver for {@link FMLInterModComms} messages + * Called between {@link Init} and {@link PostInit} + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface IMCCallback {} + /** + * Populate the annotated field with the mod instance. + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Instance { + /** + * The mod object to inject into this field + */ + String value() default ""; + } + /** + * Populate the annotated field with the mod's metadata. + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Metadata { + /** + * The mod id specifying the metadata to load here + */ + String value() default ""; + } + /** + * Populate the annotated field with an instance of the Block as specified + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Block { + /** + * The block's name + */ + String name(); + /** + * The associated ItemBlock subtype for the item (can be null for an ItemBlock) + */ + Class itemTypeClass() default ItemBlock.class; + } + /** + * Populate the annotated field with an Item + * @author cpw + * + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Item { + /** + * The name of the item + */ + String name(); + /** + * The type of the item + */ + String typeClass(); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/ModClassLoader.java b/src/minecraft/cpw/mods/fml/common/ModClassLoader.java new file mode 100644 index 0000000..b5d8bf4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ModClassLoader.java @@ -0,0 +1,89 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; +import java.util.logging.Level; + +import com.google.common.collect.ImmutableList; + +import cpw.mods.fml.common.asm.ASMTransformer; +import cpw.mods.fml.common.asm.transformers.AccessTransformer; +import cpw.mods.fml.common.modloader.BaseModProxy; +import cpw.mods.fml.relauncher.RelaunchClassLoader; + +/** + * A simple delegating class loader used to load mods into the system + * + * + * @author cpw + * + */ +public class ModClassLoader extends URLClassLoader +{ + private static final List STANDARD_LIBRARIES = ImmutableList.of("jinput.jar", "lwjgl.jar", "lwjgl_util.jar"); + private RelaunchClassLoader mainClassLoader; + + public ModClassLoader(ClassLoader parent) { + super(new URL[0], null); + this.mainClassLoader = (RelaunchClassLoader)parent; + } + + public void addFile(File modFile) throws MalformedURLException + { + URL url = modFile.toURI().toURL(); + mainClassLoader.addURL(url); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException + { + return mainClassLoader.loadClass(name); + } + + public File[] getParentSources() { + List urls=mainClassLoader.getSources(); + File[] sources=new File[urls.size()]; + try + { + for (int i = 0; i getDefaultLibraries() + { + return STANDARD_LIBRARIES; + } + + public Class loadBaseModClass(String modClazzName) throws Exception + { + AccessTransformer transformer = (AccessTransformer)mainClassLoader.getTransformers().get(0); + transformer.ensurePublicAccessFor(modClazzName); + return (Class) Class.forName(modClazzName, true, this); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/ModContainer.java b/src/minecraft/cpw/mods/fml/common/ModContainer.java new file mode 100644 index 0000000..26b4181 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ModContainer.java @@ -0,0 +1,135 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.io.File; +import java.security.cert.Certificate; +import java.util.List; +import java.util.Set; + +import com.google.common.eventbus.EventBus; + +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionRange; + +/** + * The container that wraps around mods in the system. + *

+ * The philosophy is that individual mod implementation technologies should not + * impact the actual loading and management of mod code. This interface provides + * a mechanism by which we can wrap actual mod code so that the loader and other + * facilities can treat mods at arms length. + *

+ * + * @author cpw + * + */ + +public interface ModContainer +{ + /** + * The globally unique modid for this mod + */ + String getModId(); + + /** + * A human readable name + */ + + String getName(); + + /** + * A human readable version identifier + */ + String getVersion(); + + /** + * The location on the file system which this mod came from + */ + File getSource(); + + /** + * The metadata for this mod + */ + ModMetadata getMetadata(); + + /** + * Attach this mod to it's metadata from the supplied metadata collection + */ + void bindMetadata(MetadataCollection mc); + + /** + * Set the enabled/disabled state of this mod + */ + void setEnabledState(boolean enabled); + + /** + * A list of the modids that this mod requires loaded prior to loading + */ + Set getRequirements(); + + /** + * A list of modids that should be loaded prior to this one. The special + * value * indicates to load before any other mod. + */ + List getDependencies(); + + /** + * A list of modids that should be loaded after this one. The + * special value * indicates to load after any + * other mod. + */ + List getDependants(); + + /** + * A representative string encapsulating the sorting preferences for this + * mod + */ + String getSortingRules(); + + /** + * Register the event bus for the mod and the controller for error handling + * Returns if this bus was successfully registered - disabled mods and other + * mods that don't need real events should return false and avoid further + * processing + * + * @param bus + * @param controller + */ + boolean registerBus(EventBus bus, LoadController controller); + + /** + * Does this mod match the supplied mod + * + * @param mod + */ + boolean matches(Object mod); + + /** + * Get the actual mod object + */ + Object getMod(); + + ArtifactVersion getProcessedVersion(); + + boolean isImmutable(); + + boolean isNetworkMod(); + + String getDisplayVersion(); + + VersionRange acceptableMinecraftVersionRange(); + + Certificate getSigningCertificate(); +} diff --git a/src/minecraft/cpw/mods/fml/common/ModContainerFactory.java b/src/minecraft/cpw/mods/fml/common/ModContainerFactory.java new file mode 100644 index 0000000..c57fd1d --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ModContainerFactory.java @@ -0,0 +1,58 @@ +package cpw.mods.fml.common; + +import java.io.File; +import java.util.regex.Pattern; + +import org.objectweb.asm.Type; + +import cpw.mods.fml.common.discovery.ModCandidate; +import cpw.mods.fml.common.discovery.asm.ASMModParser; +import cpw.mods.fml.common.discovery.asm.ModAnnotation; +import cpw.mods.fml.common.modloader.ModLoaderModContainer; + +public class ModContainerFactory +{ + private static Pattern modClass = Pattern.compile(".*(\\.|)(mod\\_[^\\s$]+)$"); + private static ModContainerFactory INSTANCE = new ModContainerFactory(); + public static ModContainerFactory instance() { + return INSTANCE; + } + public ModContainer build(ASMModParser modParser, File modSource, ModCandidate container) + { + String className = modParser.getASMType().getClassName(); + if (modParser.isBaseMod(container.getRememberedBaseMods()) && modClass.matcher(className).find()) + { + FMLLog.fine("Identified a BaseMod type mod %s", className); + return new ModLoaderModContainer(className, modSource, modParser.getBaseModProperties()); + } + else if (modClass.matcher(className).find()) + { + FMLLog.fine("Identified a class %s following modloader naming convention but not directly a BaseMod or currently seen subclass", className); + container.rememberModCandidateType(modParser); + } + else if (modParser.isBaseMod(container.getRememberedBaseMods())) + { + FMLLog.fine("Found a basemod %s of non-standard naming format", className); + container.rememberBaseModType(className); + } + + // We warn if it's not a basemod instance -- compatibility requires it to be in net.minecraft.src *sigh* + if (className.startsWith("net.minecraft.src.") && container.isClasspath() && !container.isMinecraftJar()) + { + FMLLog.severe("FML has detected a mod that is using a package name based on 'net.minecraft.src' : %s. This is generally a severe programming error. " + + " There should be no mod code in the minecraft namespace. MOVE YOUR MOD! If you're in eclipse, select your source code and 'refactor' it into " + + "a new package. Go on. DO IT NOW!",className); + } + + for (ModAnnotation ann : modParser.getAnnotations()) + { + if (ann.getASMType().equals(Type.getType(Mod.class))) + { + FMLLog.fine("Identified an FMLMod type mod %s", className); + return new FMLModContainer(className, modSource, ann.getValues()); + } + } + + return null; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/ModMetadata.java b/src/minecraft/cpw/mods/fml/common/ModMetadata.java new file mode 100644 index 0000000..9e1a477 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ModMetadata.java @@ -0,0 +1,164 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import static argo.jdom.JsonNodeBuilders.aStringBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +import argo.jdom.JsonNode; +import argo.jdom.JsonStringNode; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import cpw.mods.fml.common.functions.ModNameFunction; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; + +/** + * @author cpw + * + */ +public class ModMetadata +{ + private static final class JsonStringConverter implements Function + { + public Object apply(JsonNode arg0) + { + if (arg0.hasElements()) + { + return Lists.transform(arg0.getElements(), new JsonArrayConverter()); + } + else + { + return arg0.getText(); + } + } + } + + private static final class JsonArrayConverter implements Function + { + public String apply(JsonNode arg0) + { + return arg0.getText(); + } + } + + public String modId; + public String name; + public String description; + + public String url = ""; + public String updateUrl = ""; + + public String logoFile = ""; + public String version = ""; + public List authorList = Lists.newArrayList(); + public String credits = ""; + public String parent = ""; + public String[] screenshots; + + public ModContainer parentMod; + public List childMods = Lists.newArrayList(); + + public boolean useDependencyInformation; + public Set requiredMods; + public List dependencies; + public List dependants; + public boolean autogenerated; + + public ModMetadata(JsonNode node) + { + Map processedFields = Maps.transformValues(node.getFields(), new JsonStringConverter()); + modId = (String)processedFields.get(aStringBuilder("modid")); + if (Strings.isNullOrEmpty(modId)) + { + FMLLog.log(Level.SEVERE, "Found an invalid mod metadata file - missing modid"); + throw new LoaderException(); + } + name = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("name"))); + description = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("description"))); + url = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("url"))); + updateUrl = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("updateUrl"))); + logoFile = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("logoFile"))); + version = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("version"))); + credits = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("credits"))); + parent = Strings.nullToEmpty((String)processedFields.get(aStringBuilder("parent"))); + authorList = Objects.firstNonNull(((List)processedFields.get(aStringBuilder("authors"))),Objects.firstNonNull(((List)processedFields.get(aStringBuilder("authorList"))), authorList)); + requiredMods = processReferences(processedFields.get(aStringBuilder("requiredMods")), HashSet.class); + dependencies = processReferences(processedFields.get(aStringBuilder("dependencies")), ArrayList.class); + dependants = processReferences(processedFields.get(aStringBuilder("dependants")), ArrayList.class); + useDependencyInformation = Boolean.parseBoolean(Strings.nullToEmpty((String)processedFields.get(aStringBuilder("useDependencyInformation")))); + } + + public ModMetadata() + { + } + + private > T processReferences(Object refs, Class retType) + { + T res = null; + try + { + res = retType.newInstance(); + } + catch (Exception e) + { + // unpossible + } + + if (refs == null) + { + return res; + } + for (String ref : ((List)refs)) + { + res.add(VersionParser.parseVersionReference(ref)); + } + return res; + } + + public String getChildModCountString() + { + return String.format("%d child mod%s", childMods.size(), childMods.size() != 1 ? "s" : ""); + } + + public String getAuthorList() + { + return Joiner.on(", ").join(authorList); + } + + public String getChildModList() + { + return Joiner.on(", ").join(Lists.transform(childMods, new ModNameFunction())); + } + + public String printableSortingRules() + { + return ""; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/ObfuscationReflectionHelper.java b/src/minecraft/cpw/mods/fml/common/ObfuscationReflectionHelper.java new file mode 100644 index 0000000..6195e47 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ObfuscationReflectionHelper.java @@ -0,0 +1,116 @@ +/* + * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package cpw.mods.fml.common; + +import java.util.Arrays; +import java.util.logging.Level; + +import cpw.mods.fml.relauncher.ReflectionHelper; +import cpw.mods.fml.relauncher.ReflectionHelper.UnableToAccessFieldException; +import cpw.mods.fml.relauncher.ReflectionHelper.UnableToFindFieldException; + +/** + * Some reflection helper code. + * + * @author cpw + * + */ +public class ObfuscationReflectionHelper +{ + public static boolean obfuscation; + + @SuppressWarnings("unchecked") + public static T getPrivateValue(Class classToAccess, E instance, int fieldIndex) + { + try + { + return ReflectionHelper.getPrivateValue(classToAccess, instance, fieldIndex); + } + catch (UnableToAccessFieldException e) + { + FMLLog.log(Level.SEVERE, e, "There was a problem getting field index %d from %s", fieldIndex, classToAccess.getName()); + throw e; + } + } + + @SuppressWarnings("unchecked") + public static T getPrivateValue(Class classToAccess, E instance, String... fieldNames) + { + try + { + return ReflectionHelper.getPrivateValue(classToAccess, instance, fieldNames); + } + catch (UnableToFindFieldException e) + { + FMLLog.log(Level.SEVERE,e,"Unable to locate any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName()); + throw e; + } + catch (UnableToAccessFieldException e) + { + FMLLog.log(Level.SEVERE, e, "Unable to access any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName()); + throw e; + } + } + + @Deprecated + public static void setPrivateValue(Class classToAccess, T instance, int fieldIndex, E value) + { + setPrivateValue(classToAccess, instance, value, fieldIndex); + } + + public static void setPrivateValue(Class classToAccess, T instance, E value, int fieldIndex) + { + try + { + ReflectionHelper.setPrivateValue(classToAccess, instance, value, fieldIndex); + } + catch (UnableToAccessFieldException e) + { + FMLLog.log(Level.SEVERE, e, "There was a problem setting field index %d on type %s", fieldIndex, classToAccess.getName()); + throw e; + } + } + + @Deprecated + public static void setPrivateValue(Class classToAccess, T instance, String fieldName, E value) + { + setPrivateValue(classToAccess, instance, value, fieldName); + } + + public static void setPrivateValue(Class classToAccess, T instance, E value, String... fieldNames) + { + try + { + ReflectionHelper.setPrivateValue(classToAccess, instance, value, fieldNames); + } + catch (UnableToFindFieldException e) + { + FMLLog.log(Level.SEVERE, e, "Unable to locate any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName()); + throw e; + } + catch (UnableToAccessFieldException e) + { + FMLLog.log(Level.SEVERE, e, "Unable to set any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName()); + throw e; + } + } + + /** + * + */ + public static void detectObfuscation(Class clazz) + { + obfuscation = !clazz.getSimpleName().equals("World"); + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/ProxyInjector.java b/src/minecraft/cpw/mods/fml/common/ProxyInjector.java new file mode 100644 index 0000000..6b8a5a2 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/ProxyInjector.java @@ -0,0 +1,73 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.logging.Level; + +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; +import cpw.mods.fml.relauncher.Side; + +/** + * @author cpw + * + */ +public class ProxyInjector +{ + public static void inject(ModContainer mod, ASMDataTable data, Side side) + { + FMLLog.fine("Attempting to inject @SidedProxy classes into %s", mod.getModId()); + Set targets = data.getAnnotationsFor(mod).get(SidedProxy.class.getName()); + ClassLoader mcl = Loader.instance().getModClassLoader(); + + for (ASMData targ : targets) + { + try + { + Class proxyTarget = Class.forName(targ.getClassName(), true, mcl); + Field target = proxyTarget.getDeclaredField(targ.getObjectName()); + if (target == null) + { + // Impossible? + FMLLog.severe("Attempted to load a proxy type into %s.%s but the field was not found", targ.getClassName(), targ.getObjectName()); + throw new LoaderException(); + } + + String targetType = side.isClient() ? target.getAnnotation(SidedProxy.class).clientSide() : target.getAnnotation(SidedProxy.class).serverSide(); + Object proxy=Class.forName(targetType, true, mcl).newInstance(); + + if ((target.getModifiers() & Modifier.STATIC) == 0 ) + { + FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the field is not static", targetType, targ.getClassName(), targ.getObjectName()); + throw new LoaderException(); + } + if (!target.getType().isAssignableFrom(proxy.getClass())) + { + FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, targ.getClassName(), targ.getObjectName()); + throw new LoaderException(); + } + target.set(null, proxy); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "An error occured trying to load a proxy into %s.%s", targ.getAnnotationInfo(), targ.getClassName(), targ.getObjectName()); + throw new LoaderException(e); + } + } + } +} diff --git a/src/minecraft/cpw/mods/fml/common/SidedProxy.java b/src/minecraft/cpw/mods/fml/common/SidedProxy.java new file mode 100644 index 0000000..8c89d04 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/SidedProxy.java @@ -0,0 +1,44 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author cpw + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SidedProxy +{ + /** + * The name of the client side class to load and populate + */ + String clientSide() default ""; + + /** + * The name of the server side class to load and populate + */ + String serverSide() default ""; + + /** + * The name of a special bukkit plugin class to load and populate + */ + String bukkitSide() default ""; +} diff --git a/src/minecraft/cpw/mods/fml/common/SingleIntervalHandler.java b/src/minecraft/cpw/mods/fml/common/SingleIntervalHandler.java new file mode 100644 index 0000000..88d6834 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/SingleIntervalHandler.java @@ -0,0 +1,43 @@ +package cpw.mods.fml.common; + +import java.util.EnumSet; + +public class SingleIntervalHandler implements IScheduledTickHandler +{ + private ITickHandler wrapped; + public SingleIntervalHandler(ITickHandler handler) + { + this.wrapped=handler; + } + + @Override + public void tickStart(EnumSet type, Object... tickData) + { + wrapped.tickStart(type, tickData); + } + + @Override + public void tickEnd(EnumSet type, Object... tickData) + { + wrapped.tickEnd(type, tickData); + } + + @Override + public EnumSet ticks() + { + return wrapped.ticks(); + } + + @Override + public String getLabel() + { + return wrapped.getLabel(); + } + + @Override + public int nextTickSpacing() + { + return 1; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/TickType.java b/src/minecraft/cpw/mods/fml/common/TickType.java new file mode 100644 index 0000000..30a00f8 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/TickType.java @@ -0,0 +1,81 @@ +/* + * The FML Forge Mod Loader suite. + * Copyright (C) 2012 cpw + * + * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package cpw.mods.fml.common; + +import java.util.EnumSet; + +public enum TickType { + /** + * Fired during the world evaluation loop + * server and client side + * + * arg 0 : The world that is ticking + */ + WORLD, + /** + * client side + * Fired during the render processing phase + * arg 0 : float "partial render time" + */ + RENDER, + /** + * client side + * Fired during the render processing phase if a GUI is open + * arg 0 : float "partial render time" + * arg 1 : the open gui or null if no gui is open + */ + GUI, + /** + * client side + * Fired during the client evaluation loop + * arg 0 : The open gui or null if no gui is open + */ + CLIENTGUI, + /** + * server side + * Fired once as the world loads from disk + */ + WORLDLOAD, + /** + * client side only + * Fired once per client tick loop. + */ + CLIENT, + /** + * client and server side. + * Fired whenever the players update loop runs. + * arg 0 : the player + * arg 1 : the world the player is in + */ + PLAYER, + /** + * server side only. + * This is the server game tick. + * Fired once per tick loop on the server. + */ + SERVER; + + /** + * Partner ticks that are also cancelled by returning false from onTickInGame + */ + public EnumSet partnerTicks() + { + if (this==CLIENT) return EnumSet.of(RENDER); + if (this==RENDER) return EnumSet.of(CLIENT); + if (this==GUI) return EnumSet.of(CLIENTGUI); + if (this==CLIENTGUI) return EnumSet.of(GUI); + return EnumSet.noneOf(TickType.class); + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/WorldAccessContainer.java b/src/minecraft/cpw/mods/fml/common/WorldAccessContainer.java new file mode 100644 index 0000000..b2b81e1 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/WorldAccessContainer.java @@ -0,0 +1,13 @@ +package cpw.mods.fml.common; + +import java.util.Map; + +import net.minecraft.nbt.*; +import net.minecraft.world.storage.*; + + +public interface WorldAccessContainer +{ + public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info); + public void readData(SaveHandler handler, WorldInfo info, Map propertyMap, NBTTagCompound tag); +} diff --git a/src/minecraft/cpw/mods/fml/common/WrongMinecraftVersionException.java b/src/minecraft/cpw/mods/fml/common/WrongMinecraftVersionException.java new file mode 100644 index 0000000..d3352ca --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/WrongMinecraftVersionException.java @@ -0,0 +1,13 @@ +package cpw.mods.fml.common; + +public class WrongMinecraftVersionException extends RuntimeException +{ + + public ModContainer mod; + + public WrongMinecraftVersionException(ModContainer mod) + { + this.mod = mod; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/asm/ASMTransformer.java b/src/minecraft/cpw/mods/fml/common/asm/ASMTransformer.java new file mode 100644 index 0000000..a4b106e --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/ASMTransformer.java @@ -0,0 +1,31 @@ +package cpw.mods.fml.common.asm; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; + +import cpw.mods.fml.common.registry.BlockProxy; +import cpw.mods.fml.relauncher.IClassTransformer; + +public class ASMTransformer implements IClassTransformer +{ + @Override + public byte[] transform(String name, byte[] bytes) + { + if ("net.minecraft.src.Block".equals(name)) + { + ClassReader cr = new ClassReader(bytes); + ClassNode cn = new ClassNode(Opcodes.ASM4); + cr.accept(cn, ClassReader.EXPAND_FRAMES); + cn.interfaces.add(Type.getInternalName(BlockProxy.class)); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + cn.accept(cw); + return cw.toByteArray(); + } + + return bytes; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/asm/FMLSanityChecker.java b/src/minecraft/cpw/mods/fml/common/asm/FMLSanityChecker.java new file mode 100644 index 0000000..7206ee8 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/FMLSanityChecker.java @@ -0,0 +1,129 @@ +package cpw.mods.fml.common.asm; + +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.ObjectInputStream.GetField; +import java.io.StringReader; +import java.net.JarURLConnection; +import java.nio.charset.Charset; +import java.security.CodeSource; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.PKIXCertPathValidatorResult; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import javax.swing.JOptionPane; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +import cpw.mods.fml.common.CertificateHelper; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.relauncher.IFMLCallHook; +import cpw.mods.fml.relauncher.RelaunchClassLoader; + +public class FMLSanityChecker implements IFMLCallHook +{ + private static final String FMLFINGERPRINT = "AE:F6:54:79:96:E9:1B:D1:59:70:6C:B4:6B:F5:4A:89:C5:CE:08:1D".toLowerCase().replace(":",""); + private static final String FORGEFINGERPRINT = "DE:4C:F8:A3:F3:BC:15:63:58:10:04:4C:39:24:0B:F9:68:04:EA:7D".toLowerCase().replace(":", ""); + static class MLDetectorClassVisitor extends ClassVisitor + { + private boolean foundMarker = false; + private MLDetectorClassVisitor() + { + super(Opcodes.ASM4); + } + + @Override + public FieldVisitor visitField(int arg0, String arg1, String arg2, String arg3, Object arg4) + { + if ("fmlMarker".equals(arg1)) + { + foundMarker = true; + } + return null; + } + } + + private RelaunchClassLoader cl; + + @Override + public Void call() throws Exception + { + CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); + boolean goodFML = false; + if (codeSource.getLocation().getProtocol().equals("jar")) + { + Certificate[] certificates = codeSource.getCertificates(); + if (certificates!=null) + { + + for (Certificate cert : certificates) + { + String fingerprint = CertificateHelper.getFingerprint(cert); + if (fingerprint.equals(FMLFINGERPRINT)) + { + FMLLog.info("Found valid fingerprint for FML. Certificate fingerprint %s", fingerprint); + goodFML = true; + } + else if (fingerprint.equals(FORGEFINGERPRINT)) + { + FMLLog.info("Found valid fingerprint for Minecraft Forge. Certificate fingerprint %s", fingerprint); + goodFML = true; + } + else + { + FMLLog.severe("Found invalid fingerprint for FML: %s", fingerprint); + } + } + } + } + else + { + goodFML = true; + } + if (!goodFML) + { + FMLLog.severe("FML appears to be missing any signature data. This is not a good thing"); + } + byte[] mlClass = cl.getClassBytes("ModLoader"); + // Only care in obfuscated env + if (mlClass == null) + { + return null; + } + MLDetectorClassVisitor mlTester = new MLDetectorClassVisitor(); + ClassReader cr = new ClassReader(mlClass); + cr.accept(mlTester, ClassReader.SKIP_CODE); + if (!mlTester.foundMarker) + { + JOptionPane.showMessageDialog(null, "CRITICAL ERROR
" + + "ModLoader was detected in this environment
" + + "ForgeModLoader cannot be installed alongside ModLoader
" + + "All mods should work without ModLoader being installed
" + + "Because ForgeModLoader is 100% compatible with ModLoader
" + + "Re-install Minecraft Forge or Forge ModLoader into a clean
" + + "jar and try again.", + "ForgeModLoader critical error", + JOptionPane.ERROR_MESSAGE); + throw new RuntimeException("Invalid ModLoader class detected"); + } + return null; + } + + @Override + public void injectData(Map data) + { + cl = (RelaunchClassLoader) data.get("classLoader"); + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/asm/ReobfuscationMarker.java b/src/minecraft/cpw/mods/fml/common/asm/ReobfuscationMarker.java new file mode 100644 index 0000000..ac6c1d4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/ReobfuscationMarker.java @@ -0,0 +1,17 @@ +package cpw.mods.fml.common.asm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used to force certain classes to reobfuscate + * @author cpw + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ReobfuscationMarker { + +} diff --git a/src/minecraft/cpw/mods/fml/common/asm/transformers/AccessTransformer.java b/src/minecraft/cpw/mods/fml/common/asm/transformers/AccessTransformer.java new file mode 100644 index 0000000..bc1a376 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/transformers/AccessTransformer.java @@ -0,0 +1,419 @@ +package cpw.mods.fml.common.asm.transformers; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PROTECTED; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Collection; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import com.google.common.base.Charsets; +import com.google.common.base.Splitter; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.io.LineProcessor; +import com.google.common.io.Resources; + +import cpw.mods.fml.relauncher.IClassTransformer; + +public class AccessTransformer implements IClassTransformer +{ + private static final boolean DEBUG = false; + private class Modifier + { + public String name = ""; + public String desc = ""; + public int oldAccess = 0; + public int newAccess = 0; + public int targetAccess = 0; + public boolean changeFinal = false; + public boolean markFinal = false; + protected boolean modifyClassVisibility; + + private void setTargetAccess(String name) + { + if (name.startsWith("public")) targetAccess = ACC_PUBLIC; + else if (name.startsWith("private")) targetAccess = ACC_PRIVATE; + else if (name.startsWith("protected")) targetAccess = ACC_PROTECTED; + + if (name.endsWith("-f")) + { + changeFinal = true; + markFinal = false; + } + else if (name.endsWith("+f")) + { + changeFinal = true; + markFinal = true; + } + } + } + + private Multimap modifiers = ArrayListMultimap.create(); + + public AccessTransformer() throws IOException + { + this("fml_at.cfg"); + } + protected AccessTransformer(String rulesFile) throws IOException + { + readMapFile(rulesFile); + } + + private void readMapFile(String rulesFile) throws IOException + { + File file = new File(rulesFile); + URL rulesResource; + if (file.exists()) + { + rulesResource = file.toURI().toURL(); + } + else + { + rulesResource = Resources.getResource(rulesFile); + } + Resources.readLines(rulesResource, Charsets.UTF_8, new LineProcessor() + { + @Override + public Void getResult() + { + return null; + } + + @Override + public boolean processLine(String input) throws IOException + { + String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim(); + if (line.length()==0) + { + return true; + } + List parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line)); + if (parts.size()>2) + { + throw new RuntimeException("Invalid config file line "+ input); + } + Modifier m = new Modifier(); + m.setTargetAccess(parts.get(0)); + List descriptor = Lists.newArrayList(Splitter.on(".").trimResults().split(parts.get(1))); + if (descriptor.size() == 1) + { + m.modifyClassVisibility = true; + } + else + { + String nameReference = descriptor.get(1); + int parenIdx = nameReference.indexOf('('); + if (parenIdx>0) + { + m.desc = nameReference.substring(parenIdx); + m.name = nameReference.substring(0,parenIdx); + } + else + { + m.name = nameReference; + } + } + modifiers.put(descriptor.get(0).replace('/', '.'), m); + return true; + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public byte[] transform(String name, byte[] bytes) + { + if (bytes == null) { return null; } + if (!modifiers.containsKey(name)) { return bytes; } + + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(bytes); + classReader.accept(classNode, 0); + + Collection mods = modifiers.get(name); + for (Modifier m : mods) + { + if (m.modifyClassVisibility) + { + classNode.access = getFixedAccess(classNode.access, m); + if (DEBUG) + { + System.out.println(String.format("Class: %s %s -> %s", name, toBinary(m.oldAccess), toBinary(m.newAccess))); + } + continue; + } + if (m.desc.isEmpty()) + { + for (FieldNode n : (List) classNode.fields) + { + if (n.name.equals(m.name) || m.name.equals("*")) + { + n.access = getFixedAccess(n.access, m); + if (DEBUG) + { + System.out.println(String.format("Field: %s.%s %s -> %s", name, n.name, toBinary(m.oldAccess), toBinary(m.newAccess))); + } + + if (!m.name.equals("*")) + { + break; + } + } + } + } + else + { + for (MethodNode n : (List) classNode.methods) + { + if ((n.name.equals(m.name) && n.desc.equals(m.desc)) || m.name.equals("*")) + { + n.access = getFixedAccess(n.access, m); + if (DEBUG) + { + System.out.println(String.format("Method: %s.%s%s %s -> %s", name, n.name, n.desc, toBinary(m.oldAccess), toBinary(m.newAccess))); + } + + if (!m.name.equals("*")) + { + break; + } + } + } + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + private String toBinary(int num) + { + return String.format("%16s", Integer.toBinaryString(num)).replace(' ', '0'); + } + + private int getFixedAccess(int access, Modifier target) + { + target.oldAccess = access; + int t = target.targetAccess; + int ret = (access & ~7); + + switch (access & 7) + { + case ACC_PRIVATE: + ret |= t; + break; + case 0: // default + ret |= (t != ACC_PRIVATE ? t : 0 /* default */); + break; + case ACC_PROTECTED: + ret |= (t != ACC_PRIVATE && t != 0 /* default */? t : ACC_PROTECTED); + break; + case ACC_PUBLIC: + ret |= (t != ACC_PRIVATE && t != 0 /* default */&& t != ACC_PROTECTED ? t : ACC_PUBLIC); + break; + default: + throw new RuntimeException("The fuck?"); + } + + // Clear the "final" marker on fields only if specified in control field + if (target.changeFinal && target.desc == "") + { + if (target.markFinal) + { + ret |= ACC_FINAL; + } + else + { + ret &= ~ACC_FINAL; + } + } + target.newAccess = ret; + return ret; + } + + public static void main(String[] args) + { + if (args.length < 2) + { + System.out.println("Usage: AccessTransformer [MapFile2]... "); + System.exit(1); + } + + boolean hasTransformer = false; + AccessTransformer[] trans = new AccessTransformer[args.length - 1]; + for (int x = 1; x < args.length; x++) + { + try + { + trans[x - 1] = new AccessTransformer(args[x]); + hasTransformer = true; + } + catch (IOException e) + { + System.out.println("Could not read Transformer Map: " + args[x]); + e.printStackTrace(); + } + } + + if (!hasTransformer) + { + System.out.println("Culd not find a valid transformer to perform"); + System.exit(1); + } + + File orig = new File(args[0]); + File temp = new File(args[0] + ".ATBack"); + if (!orig.exists() && !temp.exists()) + { + System.out.println("Could not find target jar: " + orig); + System.exit(1); + } + + if (!orig.renameTo(temp)) + { + System.out.println("Could not rename file: " + orig + " -> " + temp); + System.exit(1); + } + + try + { + processJar(temp, orig, trans); + } + catch (IOException e) + { + e.printStackTrace(); + System.exit(1); + } + + if (!temp.delete()) + { + System.out.println("Could not delete temp file: " + temp); + } + } + + private static void processJar(File inFile, File outFile, AccessTransformer[] transformers) throws IOException + { + ZipInputStream inJar = null; + ZipOutputStream outJar = null; + + try + { + try + { + inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile))); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open input file: " + e.getMessage()); + } + + try + { + outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open output file: " + e.getMessage()); + } + + ZipEntry entry; + while ((entry = inJar.getNextEntry()) != null) + { + if (entry.isDirectory()) + { + outJar.putNextEntry(entry); + continue; + } + + byte[] data = new byte[4096]; + ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); + + int len; + do + { + len = inJar.read(data); + if (len > 0) + { + entryBuffer.write(data, 0, len); + } + } + while (len != -1); + + byte[] entryData = entryBuffer.toByteArray(); + + String entryName = entry.getName(); + + if (entryName.endsWith(".class") && !entryName.startsWith(".")) + { + ClassNode cls = new ClassNode(); + ClassReader rdr = new ClassReader(entryData); + rdr.accept(cls, 0); + String name = cls.name.replace('/', '.').replace('\\', '.'); + + for (AccessTransformer trans : transformers) + { + entryData = trans.transform(name, entryData); + } + } + + ZipEntry newEntry = new ZipEntry(entryName); + outJar.putNextEntry(newEntry); + outJar.write(entryData); + } + } + finally + { + if (outJar != null) + { + try + { + outJar.close(); + } + catch (IOException e) + { + } + } + + if (inJar != null) + { + try + { + inJar.close(); + } + catch (IOException e) + { + } + } + } + } + public void ensurePublicAccessFor(String modClazzName) + { + Modifier m = new Modifier(); + m.setTargetAccess("public"); + m.modifyClassVisibility = true; + modifiers.put(modClazzName, m); + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/asm/transformers/MCPMerger.java b/src/minecraft/cpw/mods/fml/common/asm/transformers/MCPMerger.java new file mode 100644 index 0000000..e0833c1 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/transformers/MCPMerger.java @@ -0,0 +1,637 @@ +package cpw.mods.fml.common.asm.transformers; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class MCPMerger +{ + private static Hashtable clients = new Hashtable(); + private static Hashtable shared = new Hashtable(); + private static Hashtable servers = new Hashtable(); + private static HashSet copyToServer = new HashSet(); + private static HashSet copyToClient = new HashSet(); + private static HashSet dontAnnotate = new HashSet(); + private static final boolean DEBUG = false; + + public static void main(String[] args) + { + if (args.length != 3) + { + System.out.println("Usage: MCPMerger "); + System.exit(1); + } + + File map_file = new File(args[0]); + File client_jar = new File(args[1]); + File server_jar = new File(args[2]); + File client_jar_tmp = new File(args[1] + ".MergeBack"); + File server_jar_tmp = new File(args[2] + ".MergeBack"); + + + if (client_jar_tmp.exists() && !client_jar_tmp.delete()) + { + System.out.println("Could not delete temp file: " + client_jar_tmp); + } + + if (server_jar_tmp.exists() && !server_jar_tmp.delete()) + { + System.out.println("Could not delete temp file: " + server_jar_tmp); + } + + if (!client_jar.exists()) + { + System.out.println("Could not find minecraft.jar: " + client_jar); + System.exit(1); + } + + if (!server_jar.exists()) + { + System.out.println("Could not find minecraft_server.jar: " + server_jar); + System.exit(1); + } + + if (!client_jar.renameTo(client_jar_tmp)) + { + System.out.println("Could not rename file: " + client_jar + " -> " + client_jar_tmp); + System.exit(1); + } + + if (!server_jar.renameTo(server_jar_tmp)) + { + System.out.println("Could not rename file: " + server_jar + " -> " + server_jar_tmp); + System.exit(1); + } + + if (!readMapFile(map_file)) + { + System.out.println("Could not read map file: " + map_file); + System.exit(1); + } + + try + { + processJar(client_jar_tmp, server_jar_tmp, client_jar, server_jar); + } + catch (IOException e) + { + e.printStackTrace(); + System.exit(1); + } + + if (!client_jar_tmp.delete()) + { + System.out.println("Could not delete temp file: " + client_jar_tmp); + } + + if (!server_jar_tmp.delete()) + { + System.out.println("Could not delete temp file: " + server_jar_tmp); + } + } + + private static boolean readMapFile(File mapFile) + { + try + { + FileInputStream fstream = new FileInputStream(mapFile); + DataInputStream in = new DataInputStream(fstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String line; + while ((line = br.readLine()) != null) + { + line = line.split("#")[0]; + char cmd = line.charAt(0); + line = line.substring(1).trim(); + + switch (cmd) + { + case '!': dontAnnotate.add(line); break; + case '<': copyToClient.add(line); break; + case '>': copyToServer.add(line); break; + } + } + + in.close(); + return true; + } + catch (Exception e) + { + System.err.println("Error: " + e.getMessage()); + return false; + } + } + + public static void processJar(File clientInFile, File serverInFile, File clientOutFile, File serverOutFile) throws IOException + { + ZipFile cInJar = null; + ZipFile sInJar = null; + ZipOutputStream cOutJar = null; + ZipOutputStream sOutJar = null; + + try + { + try + { + cInJar = new ZipFile(clientInFile); + sInJar = new ZipFile(serverInFile); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open input file: " + e.getMessage()); + } + try + { + cOutJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(clientOutFile))); + sOutJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(serverOutFile))); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open output file: " + e.getMessage()); + } + Hashtable cClasses = getClassEntries(cInJar, cOutJar); + Hashtable sClasses = getClassEntries(sInJar, sOutJar); + HashSet cAdded = new HashSet(); + HashSet sAdded = new HashSet(); + + for (Entry entry : cClasses.entrySet()) + { + String name = entry.getKey(); + ZipEntry cEntry = entry.getValue(); + ZipEntry sEntry = sClasses.get(name); + + if (sEntry == null) + { + if (!copyToServer.contains(name)) + { + copyClass(cInJar, cEntry, cOutJar, null, true); + cAdded.add(name); + } + else + { + if (DEBUG) + { + System.out.println("Copy class c->s : " + name); + } + copyClass(cInJar, cEntry, cOutJar, sOutJar, true); + cAdded.add(name); + sAdded.add(name); + } + continue; + } + + sClasses.remove(name); + ClassInfo info = new ClassInfo(name); + shared.put(name, info); + + byte[] cData = readEntry(cInJar, entry.getValue()); + byte[] sData = readEntry(sInJar, sEntry); + byte[] data = processClass(cData, sData, info); + + ZipEntry newEntry = new ZipEntry(cEntry.getName()); + cOutJar.putNextEntry(newEntry); + cOutJar.write(data); + sOutJar.putNextEntry(newEntry); + sOutJar.write(data); + cAdded.add(name); + sAdded.add(name); + } + + for (Entry entry : sClasses.entrySet()) + { + if (DEBUG) + { + System.out.println("Copy class s->c : " + entry.getKey()); + } + copyClass(sInJar, entry.getValue(), cOutJar, sOutJar, false); + } + + for (String name : new String[]{SideOnly.class.getName(), Side.class.getName()}) + { + String eName = name.replace(".", "/"); + byte[] data = getClassBytes(name); + ZipEntry newEntry = new ZipEntry(name.replace(".", "/").concat(".class")); + if (!cAdded.contains(eName)) + { + cOutJar.putNextEntry(newEntry); + cOutJar.write(data); + } + if (!sAdded.contains(eName)) + { + sOutJar.putNextEntry(newEntry); + sOutJar.write(data); + } + } + + } + finally + { + if (cInJar != null) + { + try { cInJar.close(); } catch (IOException e){} + } + + if (sInJar != null) + { + try { sInJar.close(); } catch (IOException e) {} + } + if (cOutJar != null) + { + try { cOutJar.close(); } catch (IOException e){} + } + + if (sOutJar != null) + { + try { sOutJar.close(); } catch (IOException e) {} + } + } + } + + private static void copyClass(ZipFile inJar, ZipEntry entry, ZipOutputStream outJar, ZipOutputStream outJar2, boolean isClientOnly) throws IOException + { + ClassReader reader = new ClassReader(readEntry(inJar, entry)); + ClassNode classNode = new ClassNode(); + + reader.accept(classNode, 0); + + if (!dontAnnotate.contains(classNode.name)) + { + if (classNode.visibleAnnotations == null) classNode.visibleAnnotations = new ArrayList(); + classNode.visibleAnnotations.add(getSideAnn(isClientOnly)); + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + byte[] data = writer.toByteArray(); + + ZipEntry newEntry = new ZipEntry(entry.getName()); + if (outJar != null) + { + outJar.putNextEntry(newEntry); + outJar.write(data); + } + if (outJar2 != null) + { + outJar2.putNextEntry(newEntry); + outJar2.write(data); + } + } + + private static AnnotationNode getSideAnn(boolean isClientOnly) + { + AnnotationNode ann = new AnnotationNode(Type.getDescriptor(SideOnly.class)); + ann.values = new ArrayList(); + ann.values.add("value"); + ann.values.add(new String[]{ Type.getDescriptor(Side.class), (isClientOnly ? "CLIENT" : "SERVER")}); + return ann; + } + + @SuppressWarnings("unchecked") + private static Hashtable getClassEntries(ZipFile inFile, ZipOutputStream outFile) throws IOException + { + Hashtable ret = new Hashtable(); + for (ZipEntry entry : Collections.list((Enumeration)inFile.entries())) + { + if (entry.isDirectory()) + { + outFile.putNextEntry(entry); + continue; + } + String entryName = entry.getName(); + if (!entryName.endsWith(".class") || entryName.startsWith(".")) + { + ZipEntry newEntry = new ZipEntry(entry.getName()); + outFile.putNextEntry(newEntry); + outFile.write(readEntry(inFile, entry)); + } + else + { + ret.put(entryName.replace(".class", ""), entry); + } + } + return ret; + } + private static byte[] readEntry(ZipFile inFile, ZipEntry entry) throws IOException + { + return readFully(inFile.getInputStream(entry)); + } + private static byte[] readFully(InputStream stream) throws IOException + { + byte[] data = new byte[4096]; + ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); + int len; + do + { + len = stream.read(data); + if (len > 0) + { + entryBuffer.write(data, 0, len); + } + } while (len != -1); + + return entryBuffer.toByteArray(); + } + private static class ClassInfo + { + public String name; + public ArrayList cField = new ArrayList(); + public ArrayList sField = new ArrayList(); + public ArrayList cMethods = new ArrayList(); + public ArrayList sMethods = new ArrayList(); + public ClassInfo(String name){ this.name = name; } + public boolean isSame() { return (cField.size() == 0 && sField.size() == 0 && cMethods.size() == 0 && sMethods.size() == 0); } + } + + public static byte[] processClass(byte[] cIn, byte[] sIn, ClassInfo info) + { + ClassNode cClassNode = getClassNode(cIn); + ClassNode sClassNode = getClassNode(sIn); + + processFields(cClassNode, sClassNode, info); + processMethods(cClassNode, sClassNode, info); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cClassNode.accept(writer); + return writer.toByteArray(); + } + + private static ClassNode getClassNode(byte[] data) + { + ClassReader reader = new ClassReader(data); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0); + return classNode; + } + + @SuppressWarnings("unchecked") + private static void processFields(ClassNode cClass, ClassNode sClass, ClassInfo info) + { + List cFields = cClass.fields; + List sFields = sClass.fields; + + int sI = 0; + for (int x = 0; x < cFields.size(); x++) + { + FieldNode cF = cFields.get(x); + if (sI < sFields.size()) + { + if (!cF.name.equals(sFields.get(sI).name)) + { + boolean serverHas = false; + for (int y = sI + 1; y < sFields.size(); y++) + { + if (cF.name.equals(sFields.get(y).name)) + { + serverHas = true; + break; + } + } + if (serverHas) + { + boolean clientHas = false; + FieldNode sF = sFields.get(sI); + for (int y = x + 1; y < cFields.size(); y++) + { + if (sF.name.equals(cFields.get(y).name)) + { + clientHas = true; + break; + } + } + if (!clientHas) + { + if (sF.visibleAnnotations == null) sF.visibleAnnotations = new ArrayList(); + sF.visibleAnnotations.add(getSideAnn(false)); + cFields.add(x++, sF); + info.sField.add(sF); + } + } + else + { + if (cF.visibleAnnotations == null) cF.visibleAnnotations = new ArrayList(); + cF.visibleAnnotations.add(getSideAnn(true)); + sFields.add(sI, cF); + info.cField.add(cF); + } + } + } + else + { + if (cF.visibleAnnotations == null) cF.visibleAnnotations = new ArrayList(); + cF.visibleAnnotations.add(getSideAnn(true)); + sFields.add(sI, cF); + info.cField.add(cF); + } + sI++; + } + if (sFields.size() != cFields.size()) + { + for (int x = cFields.size(); x < sFields.size(); x++) + { + FieldNode sF = sFields.get(x); + if (sF.visibleAnnotations == null) sF.visibleAnnotations = new ArrayList(); + sF.visibleAnnotations.add(getSideAnn(true)); + cFields.add(x++, sF); + info.sField.add(sF); + } + } + } + + private static class MethodWrapper + { + private MethodNode node; + public boolean client; + public boolean server; + public MethodWrapper(MethodNode node) + { + this.node = node; + } + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof MethodWrapper)) return false; + MethodWrapper mw = (MethodWrapper) obj; + boolean eq = Objects.equal(node.name, mw.node.name) && Objects.equal(node.desc, mw.node.desc); + if (eq) + { + mw.client = this.client | mw.client; + mw.server = this.server | mw.server; + this.client = this.client | mw.client; + this.server = this.server | mw.server; + if (DEBUG) + { + System.out.printf(" eq: %s %s\n", this, mw); + } + } + return eq; + } + + @Override + public int hashCode() + { + return Objects.hashCode(node.name, node.desc); + } + @Override + public String toString() + { + return Objects.toStringHelper(this).add("name", node.name).add("desc",node.desc).add("server",server).add("client",client).toString(); + } + } + @SuppressWarnings("unchecked") + private static void processMethods(ClassNode cClass, ClassNode sClass, ClassInfo info) + { + List cMethods = (List)cClass.methods; + List sMethods = (List)sClass.methods; + LinkedHashSet allMethods = Sets.newLinkedHashSet(); + + int cPos = 0; + int sPos = 0; + int cLen = cMethods.size(); + int sLen = sMethods.size(); + String clientName = ""; + String lastName = clientName; + String serverName = ""; + while (cPos < cLen || sPos < sLen) + { + do + { + if (sPos>=sLen) + { + break; + } + MethodNode sM = sMethods.get(sPos); + serverName = sM.name; + if (!serverName.equals(lastName) && cPos != cLen) + { + if (DEBUG) + { + System.out.printf("Server -skip : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + break; + } + MethodWrapper mw = new MethodWrapper(sM); + mw.server = true; + allMethods.add(mw); + if (DEBUG) + { + System.out.printf("Server *add* : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + sPos++; + } + while (sPos < sLen); + do + { + if (cPos>=cLen) + { + break; + } + MethodNode cM = cMethods.get(cPos); + lastName = clientName; + clientName = cM.name; + if (!clientName.equals(lastName) && sPos != sLen) + { + if (DEBUG) + { + System.out.printf("Client -skip : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + break; + } + MethodWrapper mw = new MethodWrapper(cM); + mw.client = true; + allMethods.add(mw); + if (DEBUG) + { + System.out.printf("Client *add* : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + cPos++; + } + while (cPos < cLen); + } + + cMethods.clear(); + sMethods.clear(); + + for (MethodWrapper mw : allMethods) + { + if (DEBUG) + { + System.out.println(mw); + } + cMethods.add(mw.node); + sMethods.add(mw.node); + if (mw.server && mw.client) + { + // no op + } + else + { + if (mw.node.visibleAnnotations == null) mw.node.visibleAnnotations = Lists.newArrayListWithExpectedSize(1); + mw.node.visibleAnnotations.add(getSideAnn(mw.client)); + if (mw.client) + { + info.sMethods.add(mw.node); + } + else + { + info.cMethods.add(mw.node); + } + } + } + } + + public static byte[] getClassBytes(String name) throws IOException + { + InputStream classStream = null; + try + { + classStream = MCPMerger.class.getResourceAsStream("/" + name.replace('.', '/').concat(".class")); + return readFully(classStream); + } + finally + { + if (classStream != null) + { + try + { + classStream.close(); + } + catch (IOException e){} + } + } + } +} diff --git a/src/minecraft/cpw/mods/fml/common/asm/transformers/MarkerTransformer.java b/src/minecraft/cpw/mods/fml/common/asm/transformers/MarkerTransformer.java new file mode 100644 index 0000000..f9a2985 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/transformers/MarkerTransformer.java @@ -0,0 +1,277 @@ +package cpw.mods.fml.common.asm.transformers; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import com.google.common.base.Charsets; +import com.google.common.base.Splitter; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.io.LineProcessor; +import com.google.common.io.Resources; + +import cpw.mods.fml.relauncher.IClassTransformer; + +public class MarkerTransformer implements IClassTransformer +{ + private ListMultimap markers = ArrayListMultimap.create(); + + public MarkerTransformer() throws IOException + { + this("fml_marker.cfg"); + } + protected MarkerTransformer(String rulesFile) throws IOException + { + readMapFile(rulesFile); + } + + private void readMapFile(String rulesFile) throws IOException + { + File file = new File(rulesFile); + URL rulesResource; + if (file.exists()) + { + rulesResource = file.toURI().toURL(); + } + else + { + rulesResource = Resources.getResource(rulesFile); + } + Resources.readLines(rulesResource, Charsets.UTF_8, new LineProcessor() + { + @Override + public Void getResult() + { + return null; + } + + @Override + public boolean processLine(String input) throws IOException + { + String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim(); + if (line.length()==0) + { + return true; + } + List parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line)); + if (parts.size()!=2) + { + throw new RuntimeException("Invalid config file line "+ input); + } + List markerInterfaces = Lists.newArrayList(Splitter.on(",").trimResults().split(parts.get(1))); + for (String marker : markerInterfaces) + { + markers.put(parts.get(0), marker); + } + return true; + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public byte[] transform(String name, byte[] bytes) + { + if (bytes == null) { return null; } + if (!markers.containsKey(name)) { return bytes; } + + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(bytes); + classReader.accept(classNode, 0); + + for (String marker : markers.get(name)) + { + classNode.interfaces.add(marker); + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + public static void main(String[] args) + { + if (args.length < 2) + { + System.out.println("Usage: MarkerTransformer [MapFile2]... "); + return; + } + + boolean hasTransformer = false; + MarkerTransformer[] trans = new MarkerTransformer[args.length - 1]; + for (int x = 1; x < args.length; x++) + { + try + { + trans[x - 1] = new MarkerTransformer(args[x]); + hasTransformer = true; + } + catch (IOException e) + { + System.out.println("Could not read Transformer Map: " + args[x]); + e.printStackTrace(); + } + } + + if (!hasTransformer) + { + System.out.println("Culd not find a valid transformer to perform"); + return; + } + + File orig = new File(args[0]); + File temp = new File(args[0] + ".ATBack"); + if (!orig.exists() && !temp.exists()) + { + System.out.println("Could not find target jar: " + orig); + return; + } +/* + if (temp.exists()) + { + if (orig.exists() && !orig.renameTo(new File(args[0] + (new SimpleDateFormat(".yyyy.MM.dd.HHmmss")).format(new Date())))) + { + System.out.println("Could not backup existing file: " + orig); + return; + } + if (!temp.renameTo(orig)) + { + System.out.println("Could not restore backup from previous run: " + temp); + return; + } + } +*/ + if (!orig.renameTo(temp)) + { + System.out.println("Could not rename file: " + orig + " -> " + temp); + return; + } + + try + { + processJar(temp, orig, trans); + } + catch (IOException e) + { + e.printStackTrace(); + } + + if (!temp.delete()) + { + System.out.println("Could not delete temp file: " + temp); + } + } + + private static void processJar(File inFile, File outFile, MarkerTransformer[] transformers) throws IOException + { + ZipInputStream inJar = null; + ZipOutputStream outJar = null; + + try + { + try + { + inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile))); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open input file: " + e.getMessage()); + } + + try + { + outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open output file: " + e.getMessage()); + } + + ZipEntry entry; + while ((entry = inJar.getNextEntry()) != null) + { + if (entry.isDirectory()) + { + outJar.putNextEntry(entry); + continue; + } + + byte[] data = new byte[4096]; + ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); + + int len; + do + { + len = inJar.read(data); + if (len > 0) + { + entryBuffer.write(data, 0, len); + } + } + while (len != -1); + + byte[] entryData = entryBuffer.toByteArray(); + + String entryName = entry.getName(); + + if (entryName.endsWith(".class") && !entryName.startsWith(".")) + { + ClassNode cls = new ClassNode(); + ClassReader rdr = new ClassReader(entryData); + rdr.accept(cls, 0); + String name = cls.name.replace('/', '.').replace('\\', '.'); + + for (MarkerTransformer trans : transformers) + { + entryData = trans.transform(name, entryData); + } + } + + ZipEntry newEntry = new ZipEntry(entryName); + outJar.putNextEntry(newEntry); + outJar.write(entryData); + } + } + finally + { + if (outJar != null) + { + try + { + outJar.close(); + } + catch (IOException e) + { + } + } + + if (inJar != null) + { + try + { + inJar.close(); + } + catch (IOException e) + { + } + } + } + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/asm/transformers/SideTransformer.java b/src/minecraft/cpw/mods/fml/common/asm/transformers/SideTransformer.java new file mode 100644 index 0000000..f4c63ff --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/asm/transformers/SideTransformer.java @@ -0,0 +1,105 @@ +package cpw.mods.fml.common.asm.transformers; + +import java.util.Iterator; +import java.util.List; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import cpw.mods.fml.relauncher.FMLRelauncher; +import cpw.mods.fml.relauncher.IClassTransformer; +import cpw.mods.fml.relauncher.SideOnly; + +public class SideTransformer implements IClassTransformer +{ + private static String SIDE = FMLRelauncher.side(); + private static final boolean DEBUG = false; + @SuppressWarnings("unchecked") + @Override + public byte[] transform(String name, byte[] bytes) + { + if (bytes == null) { return null; } + + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(bytes); + classReader.accept(classNode, 0); + + if (remove((List)classNode.visibleAnnotations, SIDE)) + { + if (DEBUG) + { + System.out.println(String.format("Attempted to load class %s for invalid side %s", classNode.name, SIDE)); + } + throw new RuntimeException(String.format("Attempted to load class %s for invalid side %s", classNode.name, SIDE)); + } + + Iterator fields = classNode.fields.iterator(); + while(fields.hasNext()) + { + FieldNode field = fields.next(); + if (remove((List)field.visibleAnnotations, SIDE)) + { + if (DEBUG) + { + System.out.println(String.format("Removing Field: %s.%s", classNode.name, field.name)); + } + fields.remove(); + } + } + Iterator methods = classNode.methods.iterator(); + while(methods.hasNext()) + { + MethodNode method = methods.next(); + if (remove((List)method.visibleAnnotations, SIDE)) + { + if (DEBUG) + { + System.out.println(String.format("Removing Method: %s.%s%s", classNode.name, method.name, method.desc)); + } + methods.remove(); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + private boolean remove(List anns, String side) + { + if (anns == null) + { + return false; + } + for (AnnotationNode ann : anns) + { + if (ann.desc.equals(Type.getDescriptor(SideOnly.class))) + { + if (ann.values != null) + { + for (int x = 0; x < ann.values.size() - 1; x += 2) + { + Object key = ann.values.get(x); + Object value = ann.values.get(x+1); + if (key instanceof String && key.equals("value")) + { + if (value instanceof String[] ) + { + if (!((String[])value)[1].equals(side)) + { + return true; + } + } + } + } + } + } + } + return false; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/ASMDataTable.java b/src/minecraft/cpw/mods/fml/common/discovery/ASMDataTable.java new file mode 100644 index 0000000..9812a80 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/ASMDataTable.java @@ -0,0 +1,103 @@ +package cpw.mods.fml.common.discovery; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.base.Predicate; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.SetMultimap; + +import cpw.mods.fml.common.ModContainer; + +public class ASMDataTable +{ + public static class ASMData + { + private ModCandidate candidate; + private String annotationName; + private String className; + private String objectName; + private Map annotationInfo; + public ASMData(ModCandidate candidate, String annotationName, String className, String objectName, Map info) + { + this.candidate = candidate; + this.annotationName = annotationName; + this.className = className; + this.objectName = objectName; + this.annotationInfo = info; + } + public ModCandidate getCandidate() + { + return candidate; + } + public String getAnnotationName() + { + return annotationName; + } + public String getClassName() + { + return className; + } + public String getObjectName() + { + return objectName; + } + public Map getAnnotationInfo() + { + return annotationInfo; + } + } + + private static class ModContainerPredicate implements Predicate + { + private ModContainer container; + public ModContainerPredicate(ModContainer container) + { + this.container = container; + } + public boolean apply(ASMData data) + { + return container.getSource().equals(data.candidate.getModContainer()); + } + } + private SetMultimap globalAnnotationData = HashMultimap.create(); + private Map> containerAnnotationData; + + private List containers = Lists.newArrayList(); + + public SetMultimap getAnnotationsFor(ModContainer container) + { + if (containerAnnotationData == null) + { + ImmutableMap.Builder> mapBuilder = ImmutableMap.>builder(); + for (ModContainer cont : containers) + { + Multimap values = Multimaps.filterValues(globalAnnotationData, new ModContainerPredicate(cont)); + mapBuilder.put(cont, ImmutableSetMultimap.copyOf(values)); + } + containerAnnotationData = mapBuilder.build(); + } + return containerAnnotationData.get(container); + } + + public Set getAll(String annotation) + { + return globalAnnotationData.get(annotation); + } + + public void addASMData(ModCandidate candidate, String annotation, String className, String objectName, Map annotationInfo) + { + globalAnnotationData.put(annotation, new ASMData(candidate, annotation, className, objectName, annotationInfo)); + } + + public void addContainer(ModContainer container) + { + this.containers.add(container); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/ContainerType.java b/src/minecraft/cpw/mods/fml/common/discovery/ContainerType.java new file mode 100644 index 0000000..c1c415b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/ContainerType.java @@ -0,0 +1,32 @@ +package cpw.mods.fml.common.discovery; + +import java.util.List; + +import com.google.common.base.Throwables; + +import cpw.mods.fml.common.ModContainer; + +public enum ContainerType +{ + JAR(JarDiscoverer.class), + DIR(DirectoryDiscoverer.class); + + private ITypeDiscoverer discoverer; + + private ContainerType(Class discovererClass) + { + try + { + this.discoverer = discovererClass.newInstance(); + } + catch (Exception e) + { + throw Throwables.propagate(e); + } + } + + public List findMods(ModCandidate candidate, ASMDataTable table) + { + return discoverer.discover(candidate, table); + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/discovery/DirectoryDiscoverer.java b/src/minecraft/cpw/mods/fml/common/discovery/DirectoryDiscoverer.java new file mode 100644 index 0000000..edf90e6 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/DirectoryDiscoverer.java @@ -0,0 +1,114 @@ +package cpw.mods.fml.common.discovery; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Matcher; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.common.MetadataCollection; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.ModContainerFactory; +import cpw.mods.fml.common.discovery.asm.ASMModParser; + +public class DirectoryDiscoverer implements ITypeDiscoverer +{ + private class ClassFilter implements FileFilter + { + @Override + public boolean accept(File file) + { + return (file.isFile() && classFile.matcher(file.getName()).find()) || file.isDirectory(); + } + } + + private ASMDataTable table; + + @Override + public List discover(ModCandidate candidate, ASMDataTable table) + { + this.table = table; + List found = Lists.newArrayList(); + FMLLog.fine("Examining directory %s for potential mods", candidate.getModContainer().getName()); + exploreFileSystem("", candidate.getModContainer(), found, candidate, null); + for (ModContainer mc : found) + { + table.addContainer(mc); + } + return found; + } + + public void exploreFileSystem(String path, File modDir, List harvestedMods, ModCandidate candidate, MetadataCollection mc) + { + if (path.length() == 0) + { + File metadata = new File(modDir, "mcmod.info"); + try + { + FileInputStream fis = new FileInputStream(metadata); + mc = MetadataCollection.from(fis,modDir.getName()); + fis.close(); + FMLLog.fine("Found an mcmod.info file in directory %s", modDir.getName()); + } + catch (Exception e) + { + mc = MetadataCollection.from(null,""); + FMLLog.fine("No mcmod.info file found in directory %s", modDir.getName()); + } + } + + File[] content = modDir.listFiles(new ClassFilter()); + + // Always sort our content + Arrays.sort(content); + for (File file : content) + { + if (file.isDirectory()) + { + FMLLog.finest("Recursing into package %s", path + file.getName()); + exploreFileSystem(path + file.getName() + ".", file, harvestedMods, candidate, mc); + continue; + } + Matcher match = classFile.matcher(file.getName()); + + if (match.matches()) + { + ASMModParser modParser = null; + try + { + FileInputStream fis = new FileInputStream(file); + modParser = new ASMModParser(fis); + fis.close(); + } + catch (LoaderException e) + { + FMLLog.log(Level.SEVERE, e, "There was a problem reading the file %s - probably this is a corrupt file", file.getPath()); + throw e; + } + catch (Exception e) + { + Throwables.propagate(e); + } + + modParser.validate(); + modParser.sendToTable(table, candidate); + ModContainer container = ModContainerFactory.instance().build(modParser, candidate.getModContainer(), candidate); + if (container!=null) + { + harvestedMods.add(container); + container.bindMetadata(mc); + } + } + + + } + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/ITypeDiscoverer.java b/src/minecraft/cpw/mods/fml/common/discovery/ITypeDiscoverer.java new file mode 100644 index 0000000..e67de7f --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/ITypeDiscoverer.java @@ -0,0 +1,13 @@ +package cpw.mods.fml.common.discovery; + +import java.util.List; +import java.util.regex.Pattern; + +import cpw.mods.fml.common.ModContainer; + +public interface ITypeDiscoverer +{ + public static Pattern classFile = Pattern.compile("([^\\s$]+).class$"); + + public List discover(ModCandidate candidate, ASMDataTable table); +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/JarDiscoverer.java b/src/minecraft/cpw/mods/fml/common/discovery/JarDiscoverer.java new file mode 100644 index 0000000..4bdd106 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/JarDiscoverer.java @@ -0,0 +1,95 @@ +package cpw.mods.fml.common.discovery; + +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.common.MetadataCollection; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.ModContainerFactory; +import cpw.mods.fml.common.discovery.asm.ASMModParser; + +public class JarDiscoverer implements ITypeDiscoverer +{ + @Override + public List discover(ModCandidate candidate, ASMDataTable table) + { + List foundMods = Lists.newArrayList(); + FMLLog.fine("Examining file %s for potential mods", candidate.getModContainer().getName()); + ZipFile jar = null; + try + { + jar = new ZipFile(candidate.getModContainer()); + + ZipEntry modInfo = jar.getEntry("mcmod.info"); + MetadataCollection mc = null; + if (modInfo != null) + { + FMLLog.finer("Located mcmod.info file in file %s", candidate.getModContainer().getName()); + mc = MetadataCollection.from(jar.getInputStream(modInfo), candidate.getModContainer().getName()); + } + else + { + FMLLog.fine("The mod container %s appears to be missing an mcmod.info file", candidate.getModContainer().getName()); + mc = MetadataCollection.from(null, ""); + } + for (ZipEntry ze : Collections.list(jar.entries())) + { + if (ze.getName()!=null && ze.getName().startsWith("__MACOSX")) + { + continue; + } + Matcher match = classFile.matcher(ze.getName()); + if (match.matches()) + { + ASMModParser modParser; + try + { + modParser = new ASMModParser(jar.getInputStream(ze)); + } + catch (LoaderException e) + { + FMLLog.log(Level.SEVERE, e, "There was a problem reading the entry %s in the jar %s - probably a corrupt zip", ze.getName(), candidate.getModContainer().getPath()); + jar.close(); + throw e; + } + modParser.validate(); + modParser.sendToTable(table, candidate); + ModContainer container = ModContainerFactory.instance().build(modParser, candidate.getModContainer(), candidate); + if (container!=null) + { + table.addContainer(container); + foundMods.add(container); + container.bindMetadata(mc); + } + } + } + } + catch (Exception e) + { + FMLLog.log(Level.WARNING, e, "Zip file %s failed to read properly, it will be ignored", candidate.getModContainer().getName()); + } + finally + { + if (jar != null) + { + try + { + jar.close(); + } + catch (Exception e) + { + } + } + } + return foundMods; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/ModCandidate.java b/src/minecraft/cpw/mods/fml/common/discovery/ModCandidate.java new file mode 100644 index 0000000..5abd986 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/ModCandidate.java @@ -0,0 +1,84 @@ +package cpw.mods.fml.common.discovery; + +import java.io.File; +import java.util.List; + +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.discovery.asm.ASMModParser; + + +public class ModCandidate +{ + private File classPathRoot; + private File modContainer; + private ContainerType sourceType; + private boolean classpath; + private List baseModTypes = Lists.newArrayList(); + private boolean isMinecraft; + private List baseModCandidateTypes = Lists.newArrayListWithCapacity(1); + + public ModCandidate(File classPathRoot, File modContainer, ContainerType sourceType) + { + this(classPathRoot, modContainer, sourceType, false, false); + } + public ModCandidate(File classPathRoot, File modContainer, ContainerType sourceType, boolean isMinecraft, boolean classpath) + { + this.classPathRoot = classPathRoot; + this.modContainer = modContainer; + this.sourceType = sourceType; + this.isMinecraft = isMinecraft; + this.classpath = classpath; + } + + public File getClassPathRoot() + { + return classPathRoot; + } + + public File getModContainer() + { + return modContainer; + } + + public ContainerType getSourceType() + { + return sourceType; + } + public List explore(ASMDataTable table) + { + List mods = sourceType.findMods(this, table); + if (!baseModCandidateTypes.isEmpty()) + { + FMLLog.info("Attempting to reparse the mod container %s", getModContainer().getName()); + return sourceType.findMods(this, table); + } + else + { + return mods; + } + } + + public boolean isClasspath() + { + return classpath; + } + public void rememberBaseModType(String className) + { + baseModTypes.add(className); + } + public List getRememberedBaseMods() + { + return baseModTypes; + } + public boolean isMinecraftJar() + { + return isMinecraft; + } + public void rememberModCandidateType(ASMModParser modParser) + { + baseModCandidateTypes.add(modParser); + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/discovery/ModDiscoverer.java b/src/minecraft/cpw/mods/fml/common/discovery/ModDiscoverer.java new file mode 100644 index 0000000..411eb8a --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/ModDiscoverer.java @@ -0,0 +1,136 @@ +package cpw.mods.fml.common.discovery; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.common.ModClassLoader; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.relauncher.RelaunchLibraryManager; + +public class ModDiscoverer +{ + private static Pattern zipJar = Pattern.compile("(.+).(zip|jar)$"); + + private List candidates = Lists.newArrayList(); + + private ASMDataTable dataTable = new ASMDataTable(); + + private List nonModLibs = Lists.newArrayList(); + + public void findClasspathMods(ModClassLoader modClassLoader) + { + List knownLibraries = ImmutableList.builder().addAll(modClassLoader.getDefaultLibraries()).addAll(RelaunchLibraryManager.getLibraries()).build(); + File[] minecraftSources = modClassLoader.getParentSources(); + if (minecraftSources.length == 1 && minecraftSources[0].isFile()) + { + FMLLog.fine("Minecraft is a file at %s, loading", minecraftSources[0].getAbsolutePath()); + candidates.add(new ModCandidate(minecraftSources[0], minecraftSources[0], ContainerType.JAR, true, true)); + } + else + { + for (int i = 0; i < minecraftSources.length; i++) + { + if (minecraftSources[i].isFile()) + { + if (knownLibraries.contains(minecraftSources[i].getName())) + { + FMLLog.fine("Skipping known library file %s", minecraftSources[i].getAbsolutePath()); + } + else + { + FMLLog.fine("Found a minecraft related file at %s, examining for mod candidates", minecraftSources[i].getAbsolutePath()); + candidates.add(new ModCandidate(minecraftSources[i], minecraftSources[i], ContainerType.JAR, i==0, true)); + } + } + else if (minecraftSources[i].isDirectory()) + { + FMLLog.fine("Found a minecraft related directory at %s, examining for mod candidates", minecraftSources[i].getAbsolutePath()); + candidates.add(new ModCandidate(minecraftSources[i], minecraftSources[i], ContainerType.DIR, i==0, true)); + } + } + } + + } + + public void findModDirMods(File modsDir) + { + File[] modList = modsDir.listFiles(); + // Sort the files into alphabetical order first + Arrays.sort(modList); + + for (File modFile : modList) + { + if (modFile.isDirectory()) + { + FMLLog.fine("Found a candidate mod directory %s", modFile.getName()); + candidates.add(new ModCandidate(modFile, modFile, ContainerType.DIR)); + } + else + { + Matcher matcher = zipJar.matcher(modFile.getName()); + + if (matcher.matches()) + { + FMLLog.fine("Found a candidate zip or jar file %s", matcher.group(0)); + candidates.add(new ModCandidate(modFile, modFile, ContainerType.JAR)); + } + else + { + FMLLog.fine("Ignoring unknown file %s in mods directory", modFile.getName()); + } + } + } + } + + public List identifyMods() + { + List modList = Lists.newArrayList(); + + for (ModCandidate candidate : candidates) + { + try + { + List mods = candidate.explore(dataTable); + if (mods.isEmpty() && !candidate.isClasspath()) + { + nonModLibs.add(candidate.getModContainer()); + } + else + { + modList.addAll(mods); + } + } + catch (LoaderException le) + { + FMLLog.log(Level.WARNING, le, "Identified a problem with the mod candidate %s, ignoring this source", candidate.getModContainer()); + } + catch (Throwable t) + { + Throwables.propagate(t); + } + } + + return modList; + } + + public ASMDataTable getASMTable() + { + return dataTable; + } + + public List getNonModLibs() + { + return nonModLibs; + } + +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ASMModParser.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ASMModParser.java new file mode 100644 index 0000000..21c1aa8 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ASMModParser.java @@ -0,0 +1,169 @@ +package cpw.mods.fml.common.discovery.asm; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; + +import net.minecraft.src.BaseMod; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.common.discovery.ASMDataTable; +import cpw.mods.fml.common.discovery.ModCandidate; + +public class ASMModParser +{ + + private Type asmType; + private int classVersion; + private Type asmSuperType; + private LinkedList annotations = Lists.newLinkedList(); + private String baseModProperties; + + static enum AnnotationType + { + CLASS, FIELD, METHOD, SUBTYPE; + } + + public ASMModParser(InputStream stream) throws IOException + { + try + { + ClassReader reader = new ClassReader(stream); + reader.accept(new ModClassVisitor(this), 0); + } + catch (Exception ex) + { + FMLLog.log(Level.SEVERE, ex, "Unable to read a class file correctly"); + throw new LoaderException(ex); + } + } + + public void beginNewTypeName(String typeQName, int classVersion, String superClassQName) + { + this.asmType = Type.getObjectType(typeQName); + this.classVersion = classVersion; + this.asmSuperType = Type.getObjectType(superClassQName); + } + + public void startClassAnnotation(String annotationName) + { + ModAnnotation ann = new ModAnnotation(AnnotationType.CLASS, Type.getType(annotationName), this.asmType.getClassName()); + annotations.addFirst(ann); + } + + public void addAnnotationProperty(String key, Object value) + { + annotations.getFirst().addProperty(key, value); + } + + public void startFieldAnnotation(String fieldName, String annotationName) + { + ModAnnotation ann = new ModAnnotation(AnnotationType.FIELD, Type.getType(annotationName), fieldName); + annotations.addFirst(ann); + } + + @Override + public String toString() + { + return Objects.toStringHelper("ASMAnnotationDiscoverer") + .add("className", asmType.getClassName()) + .add("classVersion", classVersion) + .add("superName", asmSuperType.getClassName()) + .add("annotations", annotations) + .add("isBaseMod", isBaseMod(Collections.emptyList())) + .add("baseModProperties", baseModProperties) + .toString(); + } + + public Type getASMType() + { + return asmType; + } + + public int getClassVersion() + { + return classVersion; + } + + public Type getASMSuperType() + { + return asmSuperType; + } + + public LinkedList getAnnotations() + { + return annotations; + } + + public void validate() + { +// if (classVersion > 50.0) +// { +// +// throw new LoaderException(new RuntimeException("Mod compiled for Java 7 detected")); +// } + } + + public boolean isBaseMod(List rememberedTypes) + { + return getASMSuperType().equals(Type.getType(BaseMod.class)) || rememberedTypes.contains(getASMSuperType().getClassName()); + } + + public void setBaseModProperties(String foundProperties) + { + this.baseModProperties = foundProperties; + } + + public String getBaseModProperties() + { + return this.baseModProperties; + } + + public void sendToTable(ASMDataTable table, ModCandidate candidate) + { + for (ModAnnotation ma : annotations) + { + table.addASMData(candidate, ma.asmType.getClassName(), this.asmType.getClassName(), ma.member, ma.values); + } + } + + public void addAnnotationArray(String name) + { + annotations.getFirst().addArray(name); + } + + public void addAnnotationEnumProperty(String name, String desc, String value) + { + annotations.getFirst().addEnumProperty(name, desc, value); + + } + + public void endArray() + { + annotations.getFirst().endArray(); + + } + + public void addSubAnnotation(String name, String desc) + { + ModAnnotation ma = annotations.getFirst(); + annotations.addFirst(ma.addChildAnnotation(name, desc)); + } + + public void endSubAnnotation() + { + // take the child and stick it at the end + ModAnnotation child = annotations.removeFirst(); + annotations.addLast(child); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotation.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotation.java new file mode 100644 index 0000000..08b3ffd --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotation.java @@ -0,0 +1,107 @@ +package cpw.mods.fml.common.discovery.asm; + +import java.util.ArrayList; +import java.util.Map; + +import org.objectweb.asm.Type; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import cpw.mods.fml.common.discovery.asm.ASMModParser.AnnotationType; + +public class ModAnnotation +{ + public class EnumHolder + { + + private String desc; + private String value; + + public EnumHolder(String desc, String value) + { + this.desc = desc; + this.value = value; + } + + } + AnnotationType type; + Type asmType; + String member; + Map values = Maps.newHashMap(); + private ArrayList arrayList; + private Object array; + private String arrayName; + private ModAnnotation parent; + public ModAnnotation(AnnotationType type, Type asmType, String member) + { + this.type = type; + this.asmType = asmType; + this.member = member; + } + + public ModAnnotation(AnnotationType type, Type asmType, ModAnnotation parent) + { + this.type = type; + this.asmType = asmType; + this.parent = parent; + } + @Override + public String toString() + { + return Objects.toStringHelper("Annotation") + .add("type",type) + .add("name",asmType.getClassName()) + .add("member",member) + .add("values", values) + .toString(); + } + public AnnotationType getType() + { + return type; + } + public Type getASMType() + { + return asmType; + } + public String getMember() + { + return member; + } + public Map getValues() + { + return values; + } + public void addArray(String name) + { + this.arrayList = Lists.newArrayList(); + this.arrayName = name; + } + public void addProperty(String key, Object value) + { + if (this.arrayList != null) + { + arrayList.add(value); + } + else + { + values.put(key, value); + } + } + + public void addEnumProperty(String key, String enumName, String value) + { + values.put(key, new EnumHolder(enumName, value)); + } + + public void endArray() + { + values.put(arrayName, arrayList); + arrayList = null; + } + public ModAnnotation addChildAnnotation(String name, String desc) + { + return new ModAnnotation(AnnotationType.SUBTYPE, Type.getType(desc), this); + } +} \ No newline at end of file diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotationVisitor.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotationVisitor.java new file mode 100644 index 0000000..533dffe --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModAnnotationVisitor.java @@ -0,0 +1,69 @@ +package cpw.mods.fml.common.discovery.asm; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Opcodes; + +public class ModAnnotationVisitor extends AnnotationVisitor +{ + private ASMModParser discoverer; + private boolean array; + private String name; + private boolean isSubAnnotation; + + public ModAnnotationVisitor(ASMModParser discoverer) + { + super(Opcodes.ASM4); + this.discoverer = discoverer; + } + + public ModAnnotationVisitor(ASMModParser discoverer, String name) + { + this(discoverer); + this.array = true; + this.name = name; + discoverer.addAnnotationArray(name); + } + + public ModAnnotationVisitor(ASMModParser discoverer, boolean isSubAnnotation) + { + this(discoverer); + this.isSubAnnotation = true; + } + + @Override + public void visit(String key, Object value) + { + discoverer.addAnnotationProperty(key, value); + } + + @Override + public void visitEnum(String name, String desc, String value) + { + discoverer.addAnnotationEnumProperty(name, desc, value); + } + + @Override + public AnnotationVisitor visitArray(String name) + { + return new ModAnnotationVisitor(discoverer, name); + } + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) + { + discoverer.addSubAnnotation(name, desc); + return new ModAnnotationVisitor(discoverer, true); + } + @Override + public void visitEnd() + { + if (array) + { + discoverer.endArray(); + } + + if (isSubAnnotation) + { + discoverer.endSubAnnotation(); + } + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java new file mode 100644 index 0000000..1786d2b --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModClassVisitor.java @@ -0,0 +1,52 @@ +package cpw.mods.fml.common.discovery.asm; + +import java.util.Collections; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +public class ModClassVisitor extends ClassVisitor +{ + private ASMModParser discoverer; + + public ModClassVisitor(ASMModParser discoverer) + { + super(Opcodes.ASM4); + this.discoverer = discoverer; + } + + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) + { + discoverer.beginNewTypeName(name, version, superName); + } + + @Override + public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible) + { + discoverer.startClassAnnotation(annotationName); + return new ModAnnotationVisitor(discoverer); + } + + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) + { + return new ModFieldVisitor(name, discoverer); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) + { + if (discoverer.isBaseMod(Collections.emptyList()) && name.equals("getPriorities") && desc.equals(Type.getMethodDescriptor(Type.getType(String.class)))) + { + return new ModMethodVisitor(name, discoverer); + } + return null; + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ModFieldVisitor.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModFieldVisitor.java new file mode 100644 index 0000000..67ef72c --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModFieldVisitor.java @@ -0,0 +1,26 @@ +package cpw.mods.fml.common.discovery.asm; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +public class ModFieldVisitor extends FieldVisitor +{ + + private String fieldName; + private ASMModParser discoverer; + + public ModFieldVisitor(String name, ASMModParser discoverer) + { + super(Opcodes.ASM4); + this.fieldName = name; + this.discoverer = discoverer; + } + + @Override + public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible) + { + discoverer.startFieldAnnotation(fieldName, annotationName); + return new ModAnnotationVisitor(discoverer); + } +} diff --git a/src/minecraft/cpw/mods/fml/common/discovery/asm/ModMethodVisitor.java b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModMethodVisitor.java new file mode 100644 index 0000000..692b7d4 --- /dev/null +++ b/src/minecraft/cpw/mods/fml/common/discovery/asm/ModMethodVisitor.java @@ -0,0 +1,61 @@ +package cpw.mods.fml.common.discovery.asm; + +import java.util.LinkedList; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import com.google.common.collect.Lists; + +public class ModMethodVisitor extends MethodVisitor +{ + + private ASMModParser discoverer; + private boolean inCode; + private LinkedList