diff --git a/build.gradle b/build.gradle index f1dbb4a..b32d466 100644 --- a/build.gradle +++ b/build.gradle @@ -20,9 +20,9 @@ plugins { id "net.minecraftforge.gradle.forge" version "2.0.2" } */ -version = "1.0" -group= "com.yourname.modid" // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = "modid" +version = "0.1.0" +group= "josephcsible.infinitefluids" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +archivesBaseName = "infinitefluids" minecraft { version = "1.10.2-12.18.0.2010" @@ -37,6 +37,12 @@ minecraft { // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. } +jar { + manifest { + attributes 'FMLCorePlugin': 'josephcsible.infinitefluids.InfiniteFluidsLoadingPlugin' + } +} + dependencies { // you may put jars on which you depend on in ./libs // or you may define them like so.. diff --git a/dummy.jar b/dummy.jar new file mode 100644 index 0000000..fe1d4d3 Binary files /dev/null and b/dummy.jar differ diff --git a/src/main/java/com/example/examplemod/ExampleMod.java b/src/main/java/com/example/examplemod/ExampleMod.java deleted file mode 100644 index f01de14..0000000 --- a/src/main/java/com/example/examplemod/ExampleMod.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.examplemod; - -import net.minecraft.init.Blocks; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.common.Mod.EventHandler; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; - -@Mod(modid = ExampleMod.MODID, version = ExampleMod.VERSION) -public class ExampleMod -{ - public static final String MODID = "examplemod"; - public static final String VERSION = "1.0"; - - @EventHandler - public void init(FMLInitializationEvent event) - { - // some example code - System.out.println("DIRT BLOCK >> "+Blocks.DIRT.getUnlocalizedName()); - } -} diff --git a/src/main/java/josephcsible/infinitefluids/InfiniteFluidsClassTransformer.java b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsClassTransformer.java new file mode 100644 index 0000000..974bdbb --- /dev/null +++ b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsClassTransformer.java @@ -0,0 +1,104 @@ +package josephcsible.infinitefluids; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import static org.objectweb.asm.Opcodes.*; + +import net.minecraft.launchwrapper.IClassTransformer; + +public class InfiniteFluidsClassTransformer implements IClassTransformer { + + private void transformUpdateTick(MethodNode mn) { + /* + We're trying to change this: + if (this.adjacentSourceBlocks >= 2 && this.blockMaterial == Material.WATER) + to this: + if (this.adjacentSourceBlocks >= 2 && InfiniteFluidsHooks.shouldCreateSourceBlock(this, worldIn, pos, state, rand)) + + Here's the relevant piece of the bytecode: + L18 + LINENUMBER 70 L18 + FRAME SAME + ALOAD 0 + GETFIELD net/minecraft/block/BlockDynamicLiquid.adjacentSourceBlocks : I + ICONST_2 *** searched for + IF_ICMPLT L22 *** searched for + ALOAD 0 *** target node + *** new stuff goes here + GETFIELD net/minecraft/block/BlockDynamicLiquid.blockMaterial : Lnet/minecraft/block/material/Material; *** removed + GETSTATIC net/minecraft/block/material/Material.WATER : Lnet/minecraft/block/material/Material; *** removed + IF_ACMPNE L22 *** removed + */ + + final String hookDesc = InfiniteFluidsLoadingPlugin.runtimeDeobfuscationEnabled ? "(Lalm;Laid;Lcm;Lars;Ljava/util/Random;)Z" : "(Lnet/minecraft/block/BlockDynamicLiquid;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)Z"; + AbstractInsnNode targetNode = null; + for (AbstractInsnNode instruction : mn.instructions.toArray()) + { + if (instruction.getOpcode() == ICONST_2 && instruction.getNext().getOpcode() == IF_ICMPLT) + { + targetNode = instruction.getNext().getNext(); // this is ALOAD 0 + break; + } + } + if (targetNode == null) + { + System.err.println("Failed to find the part of updateTick we need to patch!"); + return; + } + System.out.println("Patching updateTick"); + mn.instructions.remove(targetNode.getNext()); // remove GETFIELD + mn.instructions.remove(targetNode.getNext()); // remove GETSTATIC + JumpInsnNode n = (JumpInsnNode)targetNode.getNext(); + LabelNode ln = n.label; + mn.instructions.remove(n); // remove IF_ACMPNE + InsnList toInsert = new InsnList(); + toInsert.add(new VarInsnNode(ALOAD, 1)); + toInsert.add(new VarInsnNode(ALOAD, 2)); + toInsert.add(new VarInsnNode(ALOAD, 3)); + toInsert.add(new VarInsnNode(ALOAD, 4)); + toInsert.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(InfiniteFluidsHooks.class), "shouldCreateSourceBlock", hookDesc, false)); + toInsert.add(new JumpInsnNode(IFEQ, ln)); + mn.instructions.insert(targetNode, toInsert); + } + + private static ClassNode byteArrayToClassNode(byte[] basicClass) { + ClassNode cn = new ClassNode(); + ClassReader cr = new ClassReader(basicClass); + cr.accept(cn, 0); + return cn; + } + + private static byte[] classNodeToByteArray(ClassNode cn) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cn.accept(cw); + return cw.toByteArray(); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + if(!transformedName.equals("net.minecraft.block.BlockDynamicLiquid")) { + return basicClass; + } + ClassNode cn = byteArrayToClassNode(basicClass); + + String updateTickName, updateTickDesc; + if(InfiniteFluidsLoadingPlugin.runtimeDeobfuscationEnabled) { + updateTickName = "b"; + updateTickDesc = "(Laid;Lcm;Lars;Ljava/util/Random;)V"; + } else { + updateTickName = "updateTick"; + updateTickDesc = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V"; + } + for(MethodNode mn : cn.methods) { + if (mn.name.equals(updateTickName) && mn.desc.equals(updateTickDesc)) { + transformUpdateTick(mn); + return classNodeToByteArray(cn); + } + } + System.err.println("Failed to find the updateTick method!"); + return classNodeToByteArray(cn); + } +} diff --git a/src/main/java/josephcsible/infinitefluids/InfiniteFluidsHooks.java b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsHooks.java new file mode 100644 index 0000000..5379985 --- /dev/null +++ b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsHooks.java @@ -0,0 +1,19 @@ +package josephcsible.infinitefluids; + +import java.util.Random; + +import net.minecraft.block.BlockDynamicLiquid; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class InfiniteFluidsHooks { + public static boolean shouldCreateSourceBlock(BlockDynamicLiquid liquid, World worldIn, BlockPos pos, IBlockState state, Random rand) { + @SuppressWarnings("deprecation") // I know getMaterial is deprecated, but blockMaterial is private, and IMO this is better than reflection or an access transformer. + Material material = liquid.getMaterial(state); + if(material == Material.WATER) return true; + if(material == Material.LAVA && worldIn.provider.doesWaterVaporize()) return true; + return false; + } +} diff --git a/src/main/java/josephcsible/infinitefluids/InfiniteFluidsLoadingPlugin.java b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsLoadingPlugin.java new file mode 100644 index 0000000..3984ea4 --- /dev/null +++ b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsLoadingPlugin.java @@ -0,0 +1,37 @@ +package josephcsible.infinitefluids; + +import java.util.Map; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion; + +@MCVersion("1.10.2") +public class InfiniteFluidsLoadingPlugin implements IFMLLoadingPlugin { + + // XXX this feels hacky. Is this really the best way to keep track of this? + public static boolean runtimeDeobfuscationEnabled; + + @Override + public String[] getASMTransformerClass() { + return new String[]{InfiniteFluidsClassTransformer.class.getName()}; + } + + @Override + public String getModContainerClass() { + return InfiniteFluidsModContainer.class.getName(); + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + runtimeDeobfuscationEnabled = (Boolean) data.get("runtimeDeobfuscationEnabled"); + } + + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/java/josephcsible/infinitefluids/InfiniteFluidsModContainer.java b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsModContainer.java new file mode 100644 index 0000000..6893fd0 --- /dev/null +++ b/src/main/java/josephcsible/infinitefluids/InfiniteFluidsModContainer.java @@ -0,0 +1,27 @@ +package josephcsible.infinitefluids; + +import com.google.common.eventbus.EventBus; + +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.LoadController; +import net.minecraftforge.fml.common.ModMetadata; + +public class InfiniteFluidsModContainer extends DummyModContainer { + public InfiniteFluidsModContainer() { + super(new ModMetadata()); + ModMetadata metadata = getMetadata(); + // XXX almost all this is duplicated between here and mcmod.info + metadata.modId = "infinitefluids"; + // XXX version is duplicated between here and build.gradle + metadata.version = "0.1.0"; + metadata.name = "InfiniteFluids"; + metadata.description = "Allows fluids other than water to be infinite (i.e., turn non-source blocks next to source blocks into source blocks)."; + metadata.url = ""; + metadata.authorList.add("Joseph C. Sible"); + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + return true; // even if we don't have anything to register for, if we return false, Forge says we're not loaded + } +} diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index f480667..8782dfd 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,14 +1,14 @@ [ { - "modid": "examplemod", - "name": "Example Mod", - "description": "Example placeholder mod.", + "modid": "infinitefluids", + "name": "InfiniteFluids", + "description": "Allows fluids other than water to be infinite (i.e., turn non-source blocks next to source blocks into source blocks).", "version": "${version}", "mcversion": "${mcversion}", "url": "", "updateUrl": "", - "authorList": ["ExampleDude"], - "credits": "The Forge and FML guys, for making this example", + "authorList": ["Joseph C. Sible"], + "credits": "", "logoFile": "", "screenshots": [], "dependencies": []