In this tutorial I will try to cover some of the concepts of event handling and runtime bytecode manipulation with the help of the ASM API in forge.
Both of these techniques allow us to change the behavior of vanilla minecraft without editing the minecraft base classes directly.
This should work for 1.5.2 and 1.6.1, with the only difference so far being changing the @PreInit @Init and @PostInit with @EventHandler, and a couple of import changes. Use Ctrl+Shift+o (Oh) in eclipse to fix those.
Handling Events
-----------------
Event handling is a feature that is added by forge that allows us to hook into certain vanilla functionallity once a specific action takes place.
For the entire list of which events Forge can handle, take a look at the net.minecraftforge.event.* package.
To setup event callbacks we need to start with our main mod class and register an event handler class that we'd like to use.
@EventHandler
public void modInit(FMLInitializationEvent event) {
MinecraftForge.EVENT_BUS.register(new EntityLivingHandler());
}
In this example we are telling forge that we'd like for the class EntityLivingHandler to be the handler of specific event. You don't have to create a seperate class to handle events. You can define your main mod class to do the handling for example:
MinecraftForge.EVENT_BUS.register(this);
But we'll use the class EntityLivingHandler for clarity.
Moving on....
Inside the EntityLivingHandler class we declare the methods that handle events like so:
@ForgeSubscribe
public void onEntityLivingDeath(LivingDeathEvent event) {
First we tell forge that we want this method to be an event handler using the @ForgeSubscribe annotation.
The name of the method does not matter. It can be anything you like, what matters is the type of the parameter passed in the method. In this case it's the LivingDeathEvent class which is triggered whenever an entity dies.
So whenever an entity dies, we are passed full control by this event handler and we can go on to do whatever we like.
Examples:
SpawnEggDropsMod
This mod adds to what entities drop once killed.
CreeperBurnMod
This mod traps the LivingSpawnEvent which is called whenever an entity spawns in the world and we use it to replace the creeper with another entity.
Coremods
-----------
Prerequisite:
-------------
Make sure you download the Javadoc and documentation for ASM from http://asm.ow2.org/
To work with ASM we need to declare two classes. One that implements the IFMLLoadingPlugin interface, and the other that implements the IClassTransformer interface.
Look at the "Running the coremod in your development enviroment" section below to learn how to run a coremod
From there we can go ahead and modify vanilla with two approaches that I will explain here:
The first being patching some method from vanilla using bytecode instructions at runtime.
The second being replacing the whole vanilla class with a copy that we have stored in our jar file at runtime.
Patching a vanilla method:
----------------------------
To start we need to tell forge which class will handle the bytecode manipulation by registering the *NAME* of the transformer class.
public class EDFMLLoadingPlugin implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
//This will return the name of the class "mod.culegooner.EDClassTransformer"
return new String[]{EDClassTransformer.class.getName()};
}
}
The class that will handle the actual transformations is the class that implements IClassTransformer
public class EDClassTransformer implements IClassTransformer {
@Override
public byte[] transform(String arg0, String arg1, byte[] arg2) {
//Bytecode manipulations goes here
}
This class implements the method transform that takes 3 parameters:
public byte[] transform(String arg0, String arg1, byte[] arg2) {
with arg0 being the name of the class forge is about to work with at the JVM level
arg1 is the new name of the class (not sure what this is tbh)
arg2 you can look at it as the chuck of bytecode that's about to be loaded into JVM.
From here we check the name of arg0 and trap the one that interest us.
In this example we are interested in the net.minecraft.world.Explosion
which once it's obfuscated it's called "abq".
Note: use the files in forge/mcp/conf to see what the names are!
@Override
public byte[] transform(String arg0, String arg1, byte[] arg2) {
if (arg0.equals("abq")) {
System.out.println("********* INSIDE OBFUSCATED EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
return patchClassASM(arg0, arg2, true);
}
if (arg0.equals("net.minecraft.world.Explosion")) {
System.out.println("********* INSIDE EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
return patchClassASM(arg0, arg2, false);
}
return arg2;
}
Once we get to the traget class we call our own little method that does the modifications:
What we want to modfy in net.minecraft.world.Explosion is the method
public void doExplosionB(boolean par1)
buried inside this method a call to drop blocks after an explosion happens is issued.
If you install the ASM bytcode plugin for eclipse, you can see how it looks like in bytecode ASM instruction. at least the 1.0F / this.explosionSize, 0); part.
So let's set it up in our patchClassASM method:
(Note: once Explosion.class is obfuscated, the name of the method doExplosionB becomes "a"
This is done in multiple steps.
First we loop over the method names until we come across our target method.
Then we loop over the instructions inside that method until we come across an instruction that interests us.
public byte[] patchClassASM(String name, byte[] bytes, boolean obfuscated) {
String targetMethodName = "";
//Our target method
if(obfuscated == true)
targetMethodName ="a";
else
targetMethodName ="doExplosionB";
//set up ASM class manipulation stuff. Consult the ASM docs for details
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
//Now we loop over all of the methods declared inside the Explosion class until we get to the targetMethodName "doExplosionB"
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode m = methods.next();
int fdiv_index = -1;
//Check if this is doExplosionB and it's method signature is (Z)V which means that it accepts a boolean (Z) and returns a void (V)
if ((m.name.equals(targetMethodName) && m.desc.equals("(Z)V")))
{
System.out.println("********* Inside target method!");
AbstractInsnNode currentNode = null;
AbstractInsnNode targetNode = null;
@SuppressWarnings("unchecked")
Iterator<AbstractInsnNode> iter = m.instructions.iterator();
int index = -1;
//Loop over the instruction set and find the instruction FDIV which does the division of 1/explosionSize
while (iter.hasNext())
{
index++;
currentNode = iter.next();
//Found it! save the index location of instruction FDIV and the node for this instruction
if (currentNode.getOpcode() == FDIV)
{
targetNode = currentNode;
fdiv_index = index;
}
}
//now we want the save nods that load the variable explosionSize and the division instruction:
/*
mv.visitInsn(FCONST_1);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "net/minecraft/src/Explosion", "explosionSize", "F");
mv.visitInsn(FDIV);
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/src/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/src/World;IIIIFI)V");
*/
AbstractInsnNode remNode1 = m.instructions.get(fdiv_index-2); // mv.visitVarInsn(ALOAD, 0);
AbstractInsnNode remNode2 = m.instructions.get(fdiv_index-1); // mv.visitFieldInsn(GETFIELD, "net/minecraft/src/Explosion", "explosionSize", "F");
AbstractInsnNode remNode3 = m.instructions.get(fdiv_index); // mv.visitInsn(FDIV);
//just remove these nodes from the instruction set, this will prevent the instruction FCONST_1 to be divided.
m.instructions.remove(remNode1);
m.instructions.remove(remNode2);
m.instructions.remove(remNode3);
//in this section, i'll just illustrate how to inject a call to a static method if your instruction is a little more advanced than just removing a couple of instruction:
/*
To add new instructions, such as calling a static method can be done like so:
// make new instruction list
InsnList toInject = new InsnList();
//add your own instruction lists: *USE THE ASM JAVADOC AS REFERENCE*
toInject.add(new VarInsnNode(ALOAD, 0));
toInject.add(new MethodInsnNode(INVOKESTATIC, "mod/culegooner/MyStaticClass", "myStaticMethod", "()V"));
// add the added code to the nstruction list
// You can also choose if you want to add the code before or after the target node, check the ASM Javadoc (insertBefore)
m.instructions.insert(targetNode, toInject);
*/
System.out.println("Patching Complete!");
break;
}
}
//ASM specific for cleaning up and returning the final bytes for JVM processing.
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(writer);
return writer.toByteArray();
}
Replacing a vanilla class:
--------------------------
In this example we will replace the *WHOLE* EntityCreeper.class with one that has the following modifications:
diff -ruN forge/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java baseEdits_777/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java
--- forge/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java 2013-07-09 03:42:53.130368000 +0300
+++ baseEdits_777/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java 2013-07-09 04:30:41.259006500 +0300
@@ -19,6 +19,7 @@
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
+import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
public class EntityCreeper extends EntityMob
@@ -259,4 +260,25 @@
super.onStruckByLightning(par1EntityLightningBolt);
this.dataWatcher.updateObject(17, Byte.valueOf((byte)1));
}
+
+
+ /**
+ * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
+ * use this to react to sunlight and start to burn.
+ */
+ public void onLivingUpdate()
+ {
+ if (this.worldObj.isDaytime() && !this.worldObj.isRemote)
+ {
+ float f = this.getBrightness(1.0F);
+
+ if (f > 0.5F && this.rand.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ)))
+ {
+ this.setFire(8);
+ }
+ }
+
+ super.onLivingUpdate();
+ }
+
}
After we modify the base class run forge/mcp commands to compile and obfuscate:
recompile.bat
reobfuscate.bat --> gives the obfuscated class
reobfuscate_srg.bat --> gives the unobfuscated class
This will give us the class files:
net.minecraft.entity.monster.EntityCreeper.class
and
te.class
Note: If you have in-game runtime crash errors after applying the patch in the deobfuscated version only.
Copy the class file directly from the mcp/bin instead and not from mcp/reobf.
Example: forge/mcp/bin/minecraft/net/minecraft/entity/monster/EntityCreeper.class
open the file CreeperBurnCore_dummy.jar with winzip or 7zip and put those classes inside the jar.
Once again we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces
public class CBFMLLoadingPlugin implements IFMLLoadingPlugin {
//declare a placeholder for the name and location of the CreeperBurnCore_dummy.jar
public static File location;
@Override
public String[] getASMTransformerClass() {
////This will return the name of the class "mod.culegooner.CBClassTransformer"
return new String[]{CBClassTransformer.class.getName()};
}
@Override
public void injectData(Map<String, Object> data) {
//This will return the jar file of this mod CreeperBurnCore_dummy.jar"
location = (File) data.get("coremodLocation");
}
}
For this example, we don't want the transformer to modify a specific base method, instead we want to replace the entire class.
public class CBClassTransformer implements IClassTransformer {
@Override
public byte[] transform(String arg0, String arg1, byte[] arg2) {
//Check if the JVM is about to process the te.class or the EntityCreeper.class
if (arg0.equals("te") || arg0.equals("net.minecraft.entity.monster.EntityCreeper")) {
System.out.println("********* INSIDE CREEPER TRANSFORMER ABOUT TO PATCH: " + arg0);
arg2 = patchClassInJar(arg0, arg2, arg0, CBFMLLoadingPlugin.location);
}
return arg2;
}
//a small helper method that takes the class name we want to replace and our jar file.
//It then uses the java zip library to open up the jar file and extract the classes.
//Afterwards it serializes the class in bytes and pushes it on to the JVM.
//with the original bytes that JVM was about to process ignored completly
public byte[] patchClassInJar(String name, byte[] bytes, String ObfName, File location) {
try {
//open the jar as zip
ZipFile zip = new ZipFile(location);
//find the file inside the zip that is called te.class or net.minecraft.entity.monster.EntityCreeper.class
//replacing the . to / so it would look for net/minecraft/entity/monster/EntityCreeper.class
ZipEntry entry = zip.getEntry(name.replace('.', '/') + ".class");
if (entry == null) {
System.out.println(name + " not found in " + location.getName());
} else {
//serialize the class file into the bytes array
InputStream zin = zip.getInputStream(entry);
bytes = new byte[(int) entry.getSize()];
zin.read(bytes);
zin.close();
System.out.println("[" + "CreeperBurnCore" + "]: " + "Class " + name + " patched!");
}
zip.close();
} catch (Exception e) {
throw new RuntimeException("Error overriding " + name + " from " + location.getName(), e);
}
//return the new bytes
return bytes;
}
}
The full code for this example can be found here: CreeperBurnCore
Making the coremod appear in the mods list
----------------------------------------------
This section is just how to make the core mod appear in the lists of mods when you click on "Mods" on the main page
Going back to the class that implements IFMLLoadingPlugin add the following:
@Override
public String getModContainerClass() {
//This is the name of our dummy container "mod.culegooner.CreeperBurnCore.CBDummyContainer"
return CBDummyContainer.class.getName();
}
Then create a class that extends cpw.mods.fml.common.DummyModContainer
Running the coremod in your development enviroment
--------------------------------------------------------
This is how I run coremods in my development enviroment. There is another way using the argument fml.coreMods.load, but I never tried that. I use the approach I'm explaining here since I could also include the class patches to it as in the example of CreeperBurnCore discussed earlier.
If you still want to use the fml.coreMods.load argument, then just type the following in your VM arguments of your run configuration. If you have multiple coremods you seperate them with a comma.
First create a directory called META-INF.
Inside the META-INF folder create a file called MANIFEST.MF
Edit the file and for the line FMLCorePlugin write the fully qualified name of the class that implements the IFMLLoadingPlugin interface.
Make sure to include the other 2 lines as well.
Compress the directory into a zip using winrar or 7zip and rename the file to have the extension .jar instead of .zip (Don't compress into a .rar format!)
Now copy the new jar file to the directory /forge/mcp/jars/mods
Access Transformers
----------------------
A very quick guide to access transformers. I wasn't planning on writing one this early since I don't have a practical example to show.
Instead we'll just look at Forge itself and how it uses it.
This section as with this whole tutorial might not be 100% accurate.
Prerequisite:
-------------
You must be familliar with java reflection and that this also exists as an alternative that you can exploit.
What are they?
To describe it in the most simple terms, they are just a way of changing the access levels of vanilla classes/methods/fields
so if in vanilla you find:
private int var;
and you want to be able to access that var and modify it, then run AccessTransformer on it so it becomes:
public int var;
How it's done?
As with all the other coremods we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces.
But in the case of AccessTransformers, cpw already implemented the IClassTransformer and it's called cpw.mods.fml.common.asm.transformers.AccessTransformer
So instead of implementing your own version, you can just extend AccessTransformer!
Let's get back to the IFMLLoadingPlugin implementation in forge:
public class FMLForgePlugin implements IFMLLoadingPlugin
{
@Override
public String[] getASMTransformerClass()
{
return new String[]{"net.minecraftforge.transformers.ForgeAccessTransformer"}; }
}
And the implementation of ForgeAccessTransformer is very straight forward:
public class ForgeAccessTransformer extends AccessTransformer
{
public ForgeAccessTransformer() throws IOException
{
super("forge_at.cfg");
}
}
So it's letting AccessTransformer worry about the full implementation and it's just passing the _at.cfg file.
How is the _at.cfg written?
You can use the files in mcp/conf to find the mapping of the variables and fields that you need.
just add the access level and the field or method you want to change. To change if the field is final add the flag -f +f depending on the situation.
Look at the forge_at.cfg for examples.
But let's say you want to change the visibility of a class and it's constructor.
in the fml_cfg there's this example:
# CallableMinecraftVersion - sanity check the MC version
public c #CL:CallableMinecraftVersion
public c.<init>(Lb;)V #MD:CallableMinecraftVersion/<init>(Lnet/minecraft/src/CrashReport;) #constructor
You can find from the joined.srg the following about CallableMinecraftVersion and CrashReport:
CL: c net/minecraft/src/CallableMinecraftVersion
CL: b net/minecraft/src/CrashReport
But what about this <init>?
c.<init> is another way of saying the constructor of c
You can look at the joined.exc and see
net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V=|p_i1338_1_
so our target is:
net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V
and replacing CallableMinecraftVersion and CrashReport with the obfuscated counterparts we end up with this:
c.<init>(Lb;)V
Just to make sure that what we are doing is correct, grab the mincraft server jar minecraft_server.1.6.2.jar
and extract it to some folder.
inside where all the classes are run this command from the command line:
javap -s c.class
The output should be like so:
Compiled from "SourceFile"
class c implements java.util.concurrent.Callable {
final b a;
Signature: Lb;
c(B);
Signature: (Lb;)V
public java.lang.String a();
Signature: ()Ljava/lang/String;
public java.lang.Object call();
Signature: ()Ljava/lang/Object;
}
So that shows us that the constructor which takes a parameter c( b ) has the signature (Lb;)V
ASMifier and javap run configuration in Eclipse
=====================================
These launch scripts allow you to run javap and ASMifier on your source code inside of eclipse. The results are displayed in the Console tab in eclipse.
Hello! this is my first tutorial and my first mod! I'm not very good at doing this so be patient with me.
In this tutorial we will be overriding the Zombies and the Creepers from vanilla.
Optionally we will create a whole new mob entity that looks like Steve.
We will make the zombies be villager zombies whenever they spawn, and the creepers be sensitive to daylight and burn whenever they are in direct sunlight. Kinda similar to the CreepersNoCreeping mod by MorpheusZero
ALSO! we will be adding to what the skeleton mob drops and make it drop something if killed with a sword and something else if killed with a bow, and all of that without overriding the skeleton class!
So here goes nothing!
@Init
public void modInit(FMLInitializationEvent event) {
/*
Register a new spawn egg for your entity as follows.
TutZombie.class is the class of the entity which we will create later on. "TutZombie" is the name of the entity and the random numbers at the end are the colors of the spawn egg.
*/
EntityRegistry.registerGlobalEntityID(TutZombie.class,"TutZombie",EntityRegistry.findGlobalUniqueEntityId(), 16733525, 10066431);
/*
Register the entity to be able to spawn it, once again the parameters are the class name, name of the entity, the ID of the entity and the mod that this entity comes from. the next two numbers are the tracking range and update frequency. I've checked so many mod to see what values they use and they appear random to say the least, i decided to stick with the numbers that thaumcraft uses for their angry zombies, 64 for the tracking with update frequency 3. the last parameter is the send velocity update parameter, again all of the mods that i looked at set it to true.
*/
EntityRegistry.registerModEntity(TutZombie.class, "TutZombie", 2, this, 64, 3, true);
/*
here we do the same thing over again for our new creeper entity
*/
EntityRegistry.registerGlobalEntityID(TutCreeper.class,"TutCreeper",EntityRegistry.findGlobalUniqueEntityId(), 6750105, 7859797);
EntityRegistry.registerModEntity(TutCreeper.class, "TutCreeper", 3, this, 64, 3, true);
/*
This is the most crucial part of the whole tutorial and where the bulk of the vanilla overriding will take place.
minecraft sends events whenever something happens (spawning, killing, dancing whatever) and what this allows us to do is trap the event and modify it.
the parameter EntityLivingHandler is the name of the class that we will create later on, it can be named whatever you like, i'm sticking with the EE3 name EnitityLivingHandler
*/
MinecraftForge.EVENT_BUS.register(new EntityLivingHandler());
//language registry stuff.
LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutZombie.name", "TutZombie");
LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutCreeper.name", "TutCreeper");
}
The Zombie is very simple we want our zombie to do everything that the vanilla zombie does except to be always a villager zombie, it's only a constructor call!
public class TutZombie extends EntityZombie {
/*
this sets it to be a villager
*/
public TutZombie(World par1World) {
super(par1World);
super.setVillager(true);
}
}
The Creeper code. once again we want it to be the same as a vanilla creeper, except they behave the same as zombies and skeletons when exposed to direct sunlight. the code was copied from the SkeletonEntity
public class TutCreeper extends EntityCreeper {
public TutCreeper(World par1World) {
super(par1World);
}
public void onLivingUpdate()
{
if (this.worldObj.isDaytime() && !this.worldObj.isRemote){
float f = this.getBrightness(1.0F);
if (f > 0.5F && this.rand.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ))){
this.setFire(8); //BURN!!
}
}
super.onLivingUpdate();
}
}
EntityLivingHandler.
This is where we will kill the vanilla creeper and zombie and spawn our version of zombie and creeper.
here we will alter the drops of vanilla skeletons. you can set it to any entity if you want!
Note that the name of the class and the name of the methods doesn't matter, what matters is the type of parameter passed!
*thanks to EE3 and PetBat for the code*
public class EntityLivingHandler {
/*
this function is called whenever the entity spawns in the biome and gets pushed in the event pipeline
*/
@ForgeSubscribe
public void onEntityLivingSpawn(LivingSpawnEvent event) {
/*
make sure we are on the server
*/
if (!event.world.isRemote){
/*
is the spawn a zombie?
*/
if (event.entityLiving instanceof EntityZombie){
/*
backup the vanilla zombie spawn for later use
*/
EntityZombie b = (EntityZombie) event.entityLiving;
/*
check if this should have spawned in the first place
*/
if (b.getCanSpawnHere()){
/*
create our version of the zombie in this world
*/
TutZombie newZombie = new TutZombie(event.world);
/*
copy over the zombie coordinates and direction from the vanilla spawn over to the our spawn
*/
newZombie.setLocationAndAngles(b.posX, b.posY, b.posZ, b.rotationYaw, b.rotationPitch);
/*
spawn our new zombie
*/
event.world.spawnEntityInWorld(newZombie);
/*
kill off or despawn the vanilla zombie
*/
b.setDead();
}
}
//again the same thing is done with the creeper
if (event.entityLiving instanceof EntityCreeper){
EntityCreeper b = (EntityCreeper) event.entityLiving;
if (b.getCanSpawnHere()){
TutCreeper newCreeper = new TutCreeper(event.world);
newCreeper.setLocationAndAngles(b.posX, b.posY, b.posZ, b.rotationYaw, b.rotationPitch);
event.world.spawnEntityInWorld(newCreeper);
b.setDead();
}
}
}
}
/*
this is called whenever an entity RIP
here we'll tweak the drops
*/
@ForgeSubscribe
public void onEntityLivingDeath(LivingDeathEvent event) {
/*
find out who killed this entity
*/
if (event.source.getDamageType().equals("player")) {
/*
is the departed a monster? i.e it implementes the IMob interface? for animals they would implement the IAnimal interface
*/
if (event.entityLiving instanceof IMob){
/*
let's narrow it down further, is the deceased a skeleton?
*/
if (event.entityLiving instanceof EntitySkeleton){
double rand = Math.random();
//rand = 0.0d; //uncomment for instant results
if (rand < 0.15d) {
Random rnd = new Random();
/*
depending on the randomness, make them drop an emerald, diamond, fireballCharge
Note you will still get the regular skeleton loot of bone and arrow, these are extras!
*/
switch (rnd.nextInt(3)){
case 0:
event.entityLiving.dropItem(Item.emerald.itemID, 1);
break;
case 1:
event.entityLiving.dropItem(Item.diamond.itemID, 1);
break;
case 2:
event.entityLiving.dropItem(Item.fireballCharge.itemID, 1);
}
}
}
}
}
/*
here we check if the skeleton died by being shot at with an arrow by the player
*/
if(event.source.getSourceOfDamage() instanceof EntityArrow) {
/*
if the skeleton was shot by a dispenser this will be skipped
*/
if(((EntityArrow) event.source.getSourceOfDamage()).shootingEntity != null) {
if(((EntityArrow) event.source.getSourceOfDamage()).shootingEntity instanceof EntityPlayer) {
if(event.entityLiving instanceof IMob)
{
if(event.entityLiving instanceof EntitySkeleton){
double rand = Math.random();
//rand = 0.0d; //uncomment for instant results
if(rand < 0.15d) {
Random rnd = new Random();
//woot
switch (rnd.nextInt(3)){
case 0:
event.entityLiving.dropItem(Item.lightStoneDust.itemID, 1);
break;
case 1:
event.entityLiving.dropItem(Item.redstone.itemID, 1);
break;
case 2:
event.entityLiving.dropItem(Item.clay.itemID, 1);
}
}
}
}
}
}
}
}
}
Add this code to create the optional steve entity! dude doesn't do much, just chilling
@Init
public void modInit(FMLInitializationEvent event) {
/* Steve entity */
EntityRegistry.registerGlobalEntityID(TutEntity.class, "TutEntity", EntityRegistry.findGlobalUniqueEntityId(), 7214476, 9850659);
EntityRegistry.registerModEntity(TutEntity.class, "TutEntity", 1, this, 64, 3, true);
/*
in the next 3 lines we are registering the TutEntity (Steve) with all available biomes, you can enter each biome individually seperated by a comma or use this technic with an Array list *thanks thaumcraft*
*/
ArrayList biomes = WorldChunkManager.allowedBiomes;
BiomeGenBase[] allBiomes = (BiomeGenBase[])biomes.toArray(new BiomeGenBase[]{null});
/*
Here we are registering steve to spawn naturally as a creature with weighted probability 10, and group min max of 4, this is the same as a most vanilla monster mobs. enderman is 1,1,4. the last parameter is the biomes array created above. Again you could explicitly enter the biomes sperated by comas. you could also change the type of your entity to EnumCreatureType.monster. i think that would disable it in peaceful. not sure haven't tested it.
*/
EntityRegistry.addSpawn(TutEntity.class, 10, 4, 4,EnumCreatureType.creature,(BiomeGenBase[])biomes.toArray(allBiomes));
LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutEntity.name", "TutEntity");
}
Steve's code TutEntity
public class TutEntity extends EntityCreature {
public TutEntity(World par1World) {
super(par1World);
/*
set the texture of your mob like so or use the default vanilla /mob/char.png
*/
//this.texture = "/textures/mob/char1.png";
this.texture = "/mob/char.png";
/*
how fast they move
*/
this.moveSpeed = 0.5F;
//this.setSize(0.6F, 1.8F);
/*
some ai to make them do basic things like villagers, Copied from the villager entity in vanilla
*/
this.getNavigator().setBreakDoors(true);
this.getNavigator().setAvoidsWater(true);
this.tasks.addTask(0, new EntityAISwimming(this));
this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityZombie.class, 8.0F, 0.3F, 0.35F));
this.tasks.addTask(2, new EntityAIMoveIndoors(this));
this.tasks.addTask(3, new EntityAIRestrictOpenDoor(this));
this.tasks.addTask(4, new EntityAIOpenDoor(this, true));
this.tasks.addTask(5, new EntityAIMoveTwardsRestriction(this, 0.3F));
this.tasks.addTask(6, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
this.tasks.addTask(6, new EntityAIWatchClosest2(this, EntityVillager.class, 5.0F, 0.02F));
this.tasks.addTask(6, new EntityAIWander(this, 0.3F));
this.tasks.addTask(7, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F));
}
/*
set the health of your entity
*/
@Override
public int getMaxHealth() { return 20; }
/*
steve is passive but we will set the damage he can inflict
*/
public int getAttackStrength(){ return 4; }
/*
set the armor value for now it's hardcoded, but it should be depending on what they are wearing
*/
public int getTotalArmorValue() { return 4; }
}
Well that's it folks!
hope that made sense.
If you know how to make the code look better please let me know.
There is another way of doing what's in this tutorial using transformers (coremods)
I'll try to figure out how they work and also try to implement something similar to the practical TNT mod without modifying the base classes.
I'll post everything for you when it's done.
Ok guys here's my second tutorial and second mod. this will be insanly advanced!!! it's bordering on cracking code!!
So as I said in my previous post, I want to recreate what the Practical TNT mod does but without modifying any minecraft base files.
What the mod does is very simple, when an explosion happens, dropBlockAsItemWithChance is called with item qty chance of 1/explosionSize
we don't want that, instead we want to force to call dropBlockAsItemWithChance with qty chance of 1, meaning you'll get all of the items.
but how can we do that without modifing the Explosion class? the answer is ASM!
forge comes with ASM which allows modifying the base classes in bytecode at runtime!!
the modification does not happen at compile time.
very cool!
but regular mods cannot do that, it has to be a coremod in order to be able to do the runtime modifications of the code.
*thanks to Stalkercreeper mod for the code*
to create a coremod we need two classes.
a class the implements IFMLLoadingPlugin and another that implements IClassTransformer interfaces
/*
This is the class that implements IFMLLoadingPlugin
it does nothing except return the name of the class that implements the IClassTransformer interface
*/
import java.util.Map;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
public class TCFMLCorePlugin implements IFMLLoadingPlugin
{
@Override
public String[] getLibraryRequestClass()
{
return null;
}
@Override
public String[] getASMTransformerClass()
{
//TCTransformer is the class the implements IClassTransformer
return new String[] { "coremod.tut.common.TCTransformer" };
}
@Override
public String getModContainerClass()
{
return null;
}
@Override
public String getSetupClass()
{
return null;
}
@Override
public void injectData(Map<String, Object> data)
{
}
}
The second file where the java bytecode modifcations happens.
i used eclipse bytecode plugin and javap to try and figure out which section of code i needed to modify.
Basically we need to identify the method, then we need to find a the bytecode instruction that we need either to alter or add to. and then do our own bytecode modification or injection!
when the explosion happens the method is called as follows in bytecode in minecraft
*NOTE I DIDN'T WRITE THIS, IT'S WHAT MINECRAFT ALREADY DOES IN ASM FORMAT
what the above basically says is 1 divided by explosionSize.
on my first try i tried to inject code after the FDIV to POP the stack. that resulted in a crash. the second approach was to copy over each instruction, then tell ASM to remove them from the instruction list with the command m.instructions.remove()
and that's it!
*Note this will only work on unobfuscated minecraft, in order to make it work on obfuscated minecraft you have to find the obfuscated name of the class Explosion and it's method doExplosionB and run the code on those instead. use mcp/conf folder to see what links to what
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.FDIV;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.POP2;
import java.util.Iterator;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import cpw.mods.fml.relauncher.IClassTransformer;
public class TCTransformer implements IClassTransformer
{
private final String entityAICreeperSwellClassName = "net.minecraft.world.Explosion";
private final String entityAICreeperSwellJavaClassName = "net/minecraft/world/Explosion";
private final String methodName = "doExplosionB";
@Override
public byte[] transform(String name, String newName, byte[] bytes)
{
//System.out.println("transforming: "+name);
if (name.equals(entityAICreeperSwellClassName))
{
System.out.println("transforming: "+name);
return handleEntityAICreeperSwell(bytes);
}
return bytes;
}
private byte[] handleEntityAICreeperSwell(byte[] bytes)
{
System.out.println("**************** WOOOHOOO Explosion transform running on Explosion V3 *********************** ");
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
// find method to inject into
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode m = methods.next();
System.out.println("WOOHOO Name: "+m.name + " Desc:" + m.desc);
int fdiv_index = -1;
if (m.name.equals(shouldExecuteMethodName) && m.desc.equals("(Z)V"))
{
System.out.println("In target method! Patching!");
// find interesting instructions in method, there is a single ICONST_1 instruction we use as target
AbstractInsnNode nodeTarget = null;
System.out.println("WOOHOO m.instructions.size = " + m.instructions.size());
for (int index = 0; index < m.instructions.size(); index++)
{
AbstractInsnNode curNode = m.instructions.get(index);
System.out.println("WOOHOO index : " + index + " curNode.getOpcode() = " + curNode.getOpcode());
if (curNode.getOpcode() == FDIV)
{
nodeTarget = curNode;
fdiv_index = index;
}
}
//WOOHOO index : 336 curNode.getOpcode() = 110
System.out.println("WOOHOO new fdiv_index should be 336 -> " + fdiv_index);
if (nodeTarget == null)
{
System.out.println("Did not find all necessary target nodes! ABANDON CLASS!");
return bytes;
}
if (fdiv_index == -1)
{
System.out.println("Did not find all necessary target nodes! ABANDON CLASS!");
return bytes;
}
AbstractInsnNode remNode1 = m.instructions.get(fdiv_index-2);
AbstractInsnNode remNode2 = m.instructions.get(fdiv_index-1);
AbstractInsnNode remNode3 = m.instructions.get(fdiv_index);
m.instructions.remove(remNode1);
m.instructions.remove(remNode2);
m.instructions.remove(remNode3);
/*
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 333 curNode.getOpcode() = 12
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 334 curNode.getOpcode() = 25
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 335 curNode.getOpcode() = 180
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 336 curNode.getOpcode() = 110
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 337 curNode.getOpcode() = 3
2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 338 curNode.getOpcode() = 182
mv.visitInsn(FCONST_1);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "net/minecraft/world/Explosion", "explosionSize", "F");
mv.visitInsn(FDIV);
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/block/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/world/World;IIIIFI)V");
*/
// make new instruction list
InsnList toInject = new InsnList();
//toInject.add(new InsnNode(POP));
//toInject.add(new InsnNode(POP));
//toInject.add(new InsnNode(POP));
// inject new instruction list into method instruction list
m.instructions.insert(nodeTarget, toInject);
System.out.println("Patching Complete!");
break;
}
}
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(writer);
return writer.toByteArray();
}
}
I will be revisiting the coremod tutorial above, clean it up, explain it better and try to implement the creeper burning in bytecode. I will either be injecting the whole onLivingUpdate() method into entityCreeper class or try forcing it to call a static method on the onUpdate method. haven't decided yet, we'll see.
I'm also going to rework the drops of the entity and see if I can make them drop spawn eggs for the respective entity and maybe enhcanted books with enchantments within their min-max range.
maybe I'll create a new enchantment that can be applied to swords and bows to drop spawn eggs with a probability.
I was holding this off until the release of 1.6 which is imminent, but during my preperation I came across yet another technic of changing base classes with coremods, I thought I'd share it with you.
the technic is best illustrated by the mod Creeper Collateral http://www.minecraftforum.net/topic/1635624-152forgecoremod-creeper-collateral-get-those-blocks-back/
It's an open source mod which I never used but does exactly the same thing as practical TNT.
I have to confess I got stumped when I read the source on github since I knew it was modifying the Explosion class exactly as we did above, but I couldn't see the actual call anywhere. In the end I just went for the binary release and extracted it. And to my surprise i found that the binary jar included the file zw.class
zw.class is the obfuscated and modified version of the base class Explosion.class!
So this technic actually simplifies it further, rather than painstakenly find the opcode and figure out how and where to inject your bytecode, instead just go ahead and modify the base class, compile it, take the compiled class and put it in your coremod jar, and then issue the command to replace the entire base class file with your copy at runtime.
you can almost use the exact same code from Creeper Collateral with very minor alteration to modify any file you choose.
it can be as simple as replacing this line
if (name.equals("zw")) {
well that's it for now, as i said i'll try to implement everything I said I would above in the new version of minecraft
keep on reading code folks!
I have to confess I got stumped when I read the source on github since I knew it was modifying the Explosion class exactly as we did above, but I couldn't see the actual call anywhere.
That is because the call is actually inside denLib and not Creeper Collateral.
Thank you for the clarifications. I will be going almost line by line explaining it.
If you read my post about ASM above It was only recently that I saw people injecting code at runtime. so when I saw your mod I was looking for a call that changed a line or a method in explosion.java. I didn't expect that what it was doing was actually replace the *WHOLE* class. since i didn't know it was possible. I was very pleasantly surprised and relieved since modifying and working at opcode/bytecode level was a nightmare tbh replacing the whole class makes it way easier.
Thank you for the clarifications. I will be going almost line by line explaining it.
If you read my post about ASM above It was only recently that I saw people injecting code at runtime. so when I saw your mod I was looking for a call that changed a line or a method in explosion.java. I didn't expect that what it was doing was actually replace the *WHOLE* class. since i didn't know it was possible. I was very pleasantly surprised and relieved since modifying and working at opcode/bytecode level was a nightmare tbh replacing the whole class makes it way easier.
Bytecode editing at runtime still has its uses. There are some classes that overriding the entire thing will send the JVM up in a fireball. It also causes issues if another mod is trying to mess with that same class. (One of my other core mods used to fight with Smart Moving over who got to override the EntityPlayer class.)
I suspect that full class overriding will become more popular in 1.6 since jar modding is completely dead. If you need to edit a base class it is coremod or die.
Ok it's finally fixed!!
so there's no longer a coremod folder in forge?!!
just place the jar file inside the mods folder and run. you will see messages with a lot of astrix ******* once it patches the files.
I'll go over the code in the tutorial later on.
next stop is the AccessTransformers!
Oh special thank you to denoflions for his help. I mostly ripped off his code in CreeperBurnCore.
also a thank you to AtomicStryker and Pahimar. I used their code for CreeperBurnMod
most likely I will add an enchantment to drop spawn, but right now it's all about the concept for the tutorial.
right now every kill = 1 spawn egg.
with slime every split is a spawn egg
Total rewrite of the tutorial coming soon. most likely i'll do it in a fresh new thread. this one turned into a mess! sorry 'bout that!
Thanks, culegooner and denoflions. This is the first resource on coremods that I could find that was more than just access transformers. I really appreciate it
Thanks, culegooner and denoflions. This is the first resource on coremods that I could find that was more than just access transformers. I really appreciate it
Thank you so much! I appreciate any type of feedback. Don't copy and paste the code above since it might be broken. I have implemented almost everything in my github repo. they all work on obfuscated and unobfuscated modes for minecraft 1.6.1
Very nice tutorial, I prefer the more practical ones like this. Keep up the good work, and thanks.
I used the second CoreMod tutorial to replace some of minecraft's code while I ponder how best to submit the changes in a Forge PR.
Should really be retitled to "Replacing a vanilla class" as it goes way beyond patching and makes the two sections more distinct.
Some small issues I found:
1. The binary download of CreeperBurnCore has the path mods/culegooner/CreeperBurnCore/ but the code for download uses mods/culegooner/.
2. It assumes you're going to use ANT to build the project. Its do able without it though. Just take the binary version, delete META-INF/INDEX.LIST alter META-INF/MANIFEST.MF and mcmod.info in a text editor, and replace any java files with your own versions (might as well rename the folders to suit as well.
Very nice tutorial, I prefer the more practical ones like this. Keep up the good work, and thanks.
I used the second CoreMod tutorial to replace some of minecraft's code while I ponder how best to submit the changes in a Forge PR.
Should really be retitled to "Replacing a vanilla class" as it goes way beyond patching and makes the two sections more distinct.
Some small issues I found:
1. The binary download of CreeperBurnCore has the path mods/culegooner/CreeperBurnCore/ but the code for download uses mods/culegooner/.
2. It assumes you're going to use ANT to build the project. Its do able without it though. Just take the binary version, delete META-INF/INDEX.LIST alter META-INF/MANIFEST.MF and mcmod.info in a text editor, and replace any java files with your own versions (might as well rename the folders to suit as well.
Thank you for your feedback I'm glad people are finding it helpful. I was just learning myself and posting along the way.
did you have trouble running the CreeperBurnCore? I refactored all of the projects after I wrote the tutorial in case people decide to run them all together, that's why the package name includes the mod name as well.
As for building the jar. Yeah it's also an option to do it manually, but I tried to write the build.xml script to *almost* do everything for you, all you have to change are the values in the build.properties file to suite your needs.
TBH you don't need a knowledge of JVM bytecode to use the ASM in Forge, you just need a knowledge of the stack, some small reflection knowledge, and a tad knowledge of how the ASM opcodes work......
Knowing how the bytecode works is non-relevant really...but it would be useful to do something extravagant.....
In this tutorial I will try to cover some of the concepts of event handling and runtime bytecode manipulation with the help of the ASM API in forge.
Both of these techniques allow us to change the behavior of vanilla minecraft without editing the minecraft base classes directly.
This should work for 1.5.2 and 1.6.1, with the only difference so far being changing the @PreInit @Init and @PostInit with @EventHandler, and a couple of import changes. Use Ctrl+Shift+o (Oh) in eclipse to fix those.
Handling Events
-----------------
Event handling is a feature that is added by forge that allows us to hook into certain vanilla functionallity once a specific action takes place.
For the entire list of which events Forge can handle, take a look at the net.minecraftforge.event.* package.
To setup event callbacks we need to start with our main mod class and register an event handler class that we'd like to use.
In this example we are telling forge that we'd like for the class EntityLivingHandler to be the handler of specific event. You don't have to create a seperate class to handle events. You can define your main mod class to do the handling for example:
But we'll use the class EntityLivingHandler for clarity.
Moving on....
Inside the EntityLivingHandler class we declare the methods that handle events like so:
First we tell forge that we want this method to be an event handler using the @ForgeSubscribe annotation.
The name of the method does not matter. It can be anything you like, what matters is the type of the parameter passed in the method. In this case it's the LivingDeathEvent class which is triggered whenever an entity dies.
So whenever an entity dies, we are passed full control by this event handler and we can go on to do whatever we like.
Examples:
SpawnEggDropsMod
This mod adds to what entities drop once killed.
CreeperBurnMod
This mod traps the LivingSpawnEvent which is called whenever an entity spawns in the world and we use it to replace the creeper with another entity.
Coremods
-----------
Prerequisite:
-------------
Make sure you download the Javadoc and documentation for ASM from
http://asm.ow2.org/
And grab the ASM bytcode plugin for eclipse http://asm.ow2.org/eclipse/index.html
You must also be familiar with JVM Bytecode
To work with ASM we need to declare two classes. One that implements the IFMLLoadingPlugin interface, and the other that implements the IClassTransformer interface.
Look at the "Running the coremod in your development enviroment" section below to learn how to run a coremod
From there we can go ahead and modify vanilla with two approaches that I will explain here:
The first being patching some method from vanilla using bytecode instructions at runtime.
The second being replacing the whole vanilla class with a copy that we have stored in our jar file at runtime.
Patching a vanilla method:
----------------------------
To start we need to tell forge which class will handle the bytecode manipulation by registering the *NAME* of the transformer class.
The class that will handle the actual transformations is the class that implements IClassTransformer
This class implements the method transform that takes 3 parameters:
with arg0 being the name of the class forge is about to work with at the JVM level
arg1 is the new name of the class (not sure what this is tbh)
arg2 you can look at it as the chuck of bytecode that's about to be loaded into JVM.
From here we check the name of arg0 and trap the one that interest us.
In this example we are interested in the net.minecraft.world.Explosion
which once it's obfuscated it's called "abq".
Note: use the files in forge/mcp/conf to see what the names are!
Once we get to the traget class we call our own little method that does the modifications:
What we want to modfy in net.minecraft.world.Explosion is the method
buried inside this method a call to drop blocks after an explosion happens is issued.
by default the number of items dropped is dependent on the explosionSize variable.
We want to alter this to make it look like this instead:
If you install the ASM bytcode plugin for eclipse, you can see how it looks like in bytecode ASM instruction. at least the 1.0F / this.explosionSize, 0); part.
Read this as a stack:
So let's set it up in our patchClassASM method:
(Note: once Explosion.class is obfuscated, the name of the method doExplosionB becomes "a"
This is done in multiple steps.
First we loop over the method names until we come across our target method.
Then we loop over the instructions inside that method until we come across an instruction that interests us.
Example:
The full code for this example can be found here:
ExplosionDropsCore
Replacing a vanilla class:
--------------------------
In this example we will replace the *WHOLE* EntityCreeper.class with one that has the following modifications:
After we modify the base class run forge/mcp commands to compile and obfuscate:
recompile.bat
reobfuscate.bat --> gives the obfuscated class
reobfuscate_srg.bat --> gives the unobfuscated class
This will give us the class files:
net.minecraft.entity.monster.EntityCreeper.class
and
te.class
Note: If you have in-game runtime crash errors after applying the patch in the deobfuscated version only.
Copy the class file directly from the mcp/bin instead and not from mcp/reobf.
Example: forge/mcp/bin/minecraft/net/minecraft/entity/monster/EntityCreeper.class
open the file CreeperBurnCore_dummy.jar with winzip or 7zip and put those classes inside the jar.
the structure should end up looking like so
Once again we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces
For this example, we don't want the transformer to modify a specific base method, instead we want to replace the entire class.
The full code for this example can be found here:
CreeperBurnCore
Making the coremod appear in the mods list
----------------------------------------------
This section is just how to make the core mod appear in the lists of mods when you click on "Mods" on the main page
Going back to the class that implements IFMLLoadingPlugin add the following:
Then create a class that extends cpw.mods.fml.common.DummyModContainer
Running the coremod in your development enviroment
--------------------------------------------------------
This is how I run coremods in my development enviroment. There is another way using the argument fml.coreMods.load, but I never tried that. I use the approach I'm explaining here since I could also include the class patches to it as in the example of CreeperBurnCore discussed earlier.
If you still want to use the fml.coreMods.load argument, then just type the following in your VM arguments of your run configuration. If you have multiple coremods you seperate them with a comma.
Here's how to create the dummy jar:
First create a directory called META-INF.
Inside the META-INF folder create a file called MANIFEST.MF
Edit the file and for the line FMLCorePlugin write the fully qualified name of the class that implements the IFMLLoadingPlugin interface.
Make sure to include the other 2 lines as well.
Compress the directory into a zip using winrar or 7zip and rename the file to have the extension .jar instead of .zip (Don't compress into a .rar format!)
In the end the final jar should look like this:
Now copy the new jar file to the directory /forge/mcp/jars/mods
Access Transformers
----------------------
A very quick guide to access transformers. I wasn't planning on writing one this early since I don't have a practical example to show.
Instead we'll just look at Forge itself and how it uses it.
This section as with this whole tutorial might not be 100% accurate.
Prerequisite:
-------------
You must be familliar with java reflection and that this also exists as an alternative that you can exploit.
What are they?
To describe it in the most simple terms, they are just a way of changing the access levels of vanilla classes/methods/fields
so if in vanilla you find:
and you want to be able to access that var and modify it, then run AccessTransformer on it so it becomes:
How it's done?
As with all the other coremods we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces.
But in the case of AccessTransformers, cpw already implemented the IClassTransformer and it's called cpw.mods.fml.common.asm.transformers.AccessTransformer
So instead of implementing your own version, you can just extend AccessTransformer!
Let's get back to the IFMLLoadingPlugin implementation in forge:
And the implementation of ForgeAccessTransformer is very straight forward:
So it's letting AccessTransformer worry about the full implementation and it's just passing the _at.cfg file.
How is the _at.cfg written?
You can use the files in mcp/conf to find the mapping of the variables and fields that you need.
just add the access level and the field or method you want to change. To change if the field is final add the flag -f +f depending on the situation.
Look at the forge_at.cfg for examples.
But let's say you want to change the visibility of a class and it's constructor.
in the fml_cfg there's this example:
You can find from the joined.srg the following about CallableMinecraftVersion and CrashReport:
CL: c net/minecraft/src/CallableMinecraftVersion
CL: b net/minecraft/src/CrashReport
But what about this <init>?
c.<init> is another way of saying the constructor of c
You can look at the joined.exc and see
net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V=|p_i1338_1_
so our target is:
net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V
and replacing CallableMinecraftVersion and CrashReport with the obfuscated counterparts we end up with this:
c.<init>(Lb;)V
Just to make sure that what we are doing is correct, grab the mincraft server jar minecraft_server.1.6.2.jar
and extract it to some folder.
inside where all the classes are run this command from the command line:
The output should be like so:
So that shows us that the constructor which takes a parameter c( b ) has the signature (Lb;)V
ASMifier and javap run configuration in Eclipse
=====================================
These launch scripts allow you to run javap and ASMifier on your source code inside of eclipse. The results are displayed in the Console tab in eclipse.
just copy the *.launch files to
forge/mcp/eclipse/.metadata/.plugins/org.eclipse.debug.core/.launches/
the launch configuration can be downloaded from here:
EclipseLaunchers
Special thanks the following people who I used their code as the basis of this tutorial:
AtomicStryker
Pahimar
denoflions
Full code for this tutorial can be found at my
github repo
*For the coremod tutorial see the second post!*
Hello! this is my first tutorial and my first mod! I'm not very good at doing this so be patient with me.
In this tutorial we will be overriding the Zombies and the Creepers from vanilla.
Optionally we will create a whole new mob entity that looks like Steve.
We will make the zombies be villager zombies whenever they spawn, and the creepers be sensitive to daylight and burn whenever they are in direct sunlight. Kinda similar to the CreepersNoCreeping mod by MorpheusZero
ALSO! we will be adding to what the skeleton mob drops and make it drop something if killed with a sword and something else if killed with a bow, and all of that without overriding the skeleton class!
So here goes nothing!
The Zombie is very simple we want our zombie to do everything that the vanilla zombie does except to be always a villager zombie, it's only a constructor call!
The Creeper code. once again we want it to be the same as a vanilla creeper, except they behave the same as zombies and skeletons when exposed to direct sunlight. the code was copied from the SkeletonEntity
EntityLivingHandler.
This is where we will kill the vanilla creeper and zombie and spawn our version of zombie and creeper.
here we will alter the drops of vanilla skeletons. you can set it to any entity if you want!
Note that the name of the class and the name of the methods doesn't matter, what matters is the type of parameter passed!
*thanks to EE3 and PetBat for the code*
Add this code to create the optional steve entity! dude doesn't do much, just chilling
Steve's code TutEntity
Well that's it folks!
hope that made sense.
If you know how to make the code look better please let me know.
Links
CreepersNoCreeping by MorpheusZero
http://www.minecraft...ads-april-2013/
An Excellet tutorial to get you started, heavily based on EE3 code
http://www.minecraft...aking-requests/
An Excellent tutorial for changing block behavior without editing base classes
http://www.minecraft...classes-simple/
Mob drop technic discussed in this tutorial comes directly from EE3
http://www.minecraft...change-3-pre1h/
Mob overriding comes from the nifty mod PetBat
http://www.minecraft...y-hang-get-him/
I'll try to figure out how they work and also try to implement something similar to the practical TNT mod without modifying the base classes.
I'll post everything for you when it's done.
for the mean time practical TNT mod can be found here http://www.minecraft...-practical-tnt/
Ok guys here's my second tutorial and second mod. this will be insanly advanced!!! it's bordering on cracking code!!
So as I said in my previous post, I want to recreate what the Practical TNT mod does but without modifying any minecraft base files.
What the mod does is very simple, when an explosion happens, dropBlockAsItemWithChance is called with item qty chance of 1/explosionSize
we don't want that, instead we want to force to call dropBlockAsItemWithChance with qty chance of 1, meaning you'll get all of the items.
but how can we do that without modifing the Explosion class? the answer is ASM!
forge comes with ASM which allows modifying the base classes in bytecode at runtime!!
the modification does not happen at compile time.
very cool!
but regular mods cannot do that, it has to be a coremod in order to be able to do the runtime modifications of the code.
*thanks to Stalkercreeper mod for the code*
to create a coremod we need two classes.
a class the implements IFMLLoadingPlugin and another that implements IClassTransformer interfaces
The second file where the java bytecode modifcations happens.
i used eclipse bytecode plugin and javap to try and figure out which section of code i needed to modify.
Basically we need to identify the method, then we need to find a the bytecode instruction that we need either to alter or add to. and then do our own bytecode modification or injection!
when the explosion happens the method is called as follows in bytecode in minecraft
*NOTE I DIDN'T WRITE THIS, IT'S WHAT MINECRAFT ALREADY DOES IN ASM FORMAT
you read this as a stack!
what the above basically says is 1 divided by explosionSize.
on my first try i tried to inject code after the FDIV to POP the stack. that resulted in a crash. the second approach was to copy over each instruction, then tell ASM to remove them from the instruction list with the command m.instructions.remove()
and that's it!
*Note this will only work on unobfuscated minecraft, in order to make it work on obfuscated minecraft you have to find the obfuscated name of the class Explosion and it's method doExplosionB and run the code on those instead. use mcp/conf folder to see what links to what
I will be revisiting the coremod tutorial above, clean it up, explain it better and try to implement the creeper burning in bytecode. I will either be injecting the whole onLivingUpdate() method into entityCreeper class or try forcing it to call a static method on the onUpdate method. haven't decided yet, we'll see.
I'm also going to rework the drops of the entity and see if I can make them drop spawn eggs for the respective entity and maybe enhcanted books with enchantments within their min-max range.
maybe I'll create a new enchantment that can be applied to swords and bows to drop spawn eggs with a probability.
I was holding this off until the release of 1.6 which is imminent, but during my preperation I came across yet another technic of changing base classes with coremods, I thought I'd share it with you.
the technic is best illustrated by the mod Creeper Collateral http://www.minecraftforum.net/topic/1635624-152forgecoremod-creeper-collateral-get-those-blocks-back/
It's an open source mod which I never used but does exactly the same thing as practical TNT.
I have to confess I got stumped when I read the source on github since I knew it was modifying the Explosion class exactly as we did above, but I couldn't see the actual call anywhere. In the end I just went for the binary release and extracted it. And to my surprise i found that the binary jar included the file zw.class
zw.class is the obfuscated and modified version of the base class Explosion.class!
So this technic actually simplifies it further, rather than painstakenly find the opcode and figure out how and where to inject your bytecode, instead just go ahead and modify the base class, compile it, take the compiled class and put it in your coremod jar, and then issue the command to replace the entire base class file with your copy at runtime.
you can almost use the exact same code from Creeper Collateral with very minor alteration to modify any file you choose.
it can be as simple as replacing this line
well that's it for now, as i said i'll try to implement everything I said I would above in the new version of minecraft
keep on reading code folks!
Here's my first official release of a mod!
this is just the creeper burning in daytime. I used the events method. I will be doing another one for the coremods method
This is for minecraft + forge 1.6.1
https://github.com/culegooner/CreeperBurnMod
That is because the call is actually inside denLib and not Creeper Collateral.
Classes you need to look at to understand it:
Explosion class replacer:
https://github.com/d...mExplosion.java
IFMLLoadingPlugin that tells FML where the class replacer is:
https://github.com/d...CCCore.java#L21
Base class overrider in denLib:
https://github.com/d...ssOverride.java
(hint: An almost exact replica of that overrider is found in CodeChickenCore. NEI did the base class overriding trick long before me.)
If you read my post about ASM above It was only recently that I saw people injecting code at runtime. so when I saw your mod I was looking for a call that changed a line or a method in explosion.java. I didn't expect that what it was doing was actually replace the *WHOLE* class. since i didn't know it was possible. I was very pleasantly surprised and relieved since modifying and working at opcode/bytecode level was a nightmare tbh replacing the whole class makes it way easier.
Bytecode editing at runtime still has its uses. There are some classes that overriding the entire thing will send the JVM up in a fireball. It also causes issues if another mod is trying to mess with that same class. (One of my other core mods used to fight with Smart Moving over who got to override the EntityPlayer class.)
I suspect that full class overriding will become more popular in 1.6 since jar modding is completely dead. If you need to edit a base class it is coremod or die.
This still doesn't work correctly
what it's *supposed* to do is replace the EntityCreeper.class or the obfuscated tc.class with the version that's in the jar.
The modification that I did to the EntityCreeper.java was adding the following code:
it's not running as expected yet. i'll try to figure out why and post it here.
https://github.com/culegooner/CreeperBurnCore
so there's no longer a coremod folder in forge?!!
just place the jar file inside the mods folder and run. you will see messages with a lot of astrix ******* once it patches the files.
I'll go over the code in the tutorial later on.
next stop is the AccessTransformers!
also a thank you to AtomicStryker and Pahimar. I used their code for CreeperBurnMod
I'm almost done with these mods, just need to create another events mod to add to the drops. and then a complete rewrite of the tutorial.
Later I'll do something that uses AccessTransformer (changing visibility)
you can download the binary to all of the mods by clicking on the release tab in github.
link to the latest mod:
https://github.com/culegooner/ExplosionDropsCore
https://github.com/culegooner/SpawnEggDropsMod
most likely I will add an enchantment to drop spawn, but right now it's all about the concept for the tutorial.
right now every kill = 1 spawn egg.
with slime every split is a spawn egg
Total rewrite of the tutorial coming soon. most likely i'll do it in a fresh new thread. this one turned into a mess! sorry 'bout that!
Thank you so much! I appreciate any type of feedback. Don't copy and paste the code above since it might be broken. I have implemented almost everything in my github repo. they all work on obfuscated and unobfuscated modes for minecraft 1.6.1
The code can be found here:
https://github.com/culegooner/
Also I should say that before working with ASM, I watched the following and it helped me a lot:
I used the second CoreMod tutorial to replace some of minecraft's code while I ponder how best to submit the changes in a Forge PR.
Should really be retitled to "Replacing a vanilla class" as it goes way beyond patching and makes the two sections more distinct.
Some small issues I found:
1. The binary download of CreeperBurnCore has the path mods/culegooner/CreeperBurnCore/ but the code for download uses mods/culegooner/.
2. It assumes you're going to use ANT to build the project. Its do able without it though. Just take the binary version, delete META-INF/INDEX.LIST alter META-INF/MANIFEST.MF and mcmod.info in a text editor, and replace any java files with your own versions (might as well rename the folders to suit as well.
Thank you for your feedback I'm glad people are finding it helpful. I was just learning myself and posting along the way.
did you have trouble running the CreeperBurnCore? I refactored all of the projects after I wrote the tutorial in case people decide to run them all together, that's why the package name includes the mod name as well.
As for building the jar. Yeah it's also an option to do it manually, but I tried to write the build.xml script to *almost* do everything for you, all you have to change are the values in the build.properties file to suite your needs.
and if you don't want the INDEX.LIST file
just modify the build.xml file at lines 61 and 85 are remove-> index="true"
Knowing how the bytecode works is non-relevant really...but it would be useful to do something extravagant.....