Skip to content

Instantly share code, notes, and snippets.

@ShetiPhian
Created July 2, 2017 13:37
Embed
What would you like to do?
ShetiPhian-ASM Source and Explanation
Q: What does this do?
A: Adds a hook to Entity.createRunningParticles() that enabled me to override the run particles of my blocks.
Q: Where is it being used?
A:
- Platforms:platform uses it to change the particle texture into the material the platform is made from, otherwise it will always be oak.
- Terraqueous:pergola uses it to colorize the particles from white to the color of the pergola.
- Terraqueous:flowerpot uses it to colorize the particles, also used on the planter to texture the particles based on the block inside.
Q: Why is this a coremod?
A: I tried to get this into Forge.
They already add Block.addLandingEffects and Block.addHitEffects that cover falling, place and break, only run particles can not be controlled.
I opened a pull request Feb/6/2016 to add Block.addRunningEffects.
That pull was kept upto date and conflict free until I closed it May/24/2016 as Forge migrated to Minecraft 1.9.
I had opened a 1.9 verison of the pull request on May/10/2016, this was monitored for another month to make sure it stayed conflict free.
Eventually giving up I took the time to learn enough ASM to create ShetiPhian-ASM.
If I was to attempt to get the pull accepted again, I'd do it slightly different.
I'd still add the block patch as is:
---
/**
* Allows a block to override the standard Entity.createRunningParticles particles.
*
* @param state State at the specific world/pos
* @param world The current world
* @param pos Position of the block that the entity is running on.
* @param entity The entity that is running on the block.
* @return True to prevent vanilla running particles form spawning.
*/
public boolean addRunningEffects(IBlockState state, World world, BlockPos pos, Entity entity)
{
return false;
}
---
The Entity.createRunningParticles patch would be a single line at the very top pointing to the ForgeHooksClient class
---
protected void createRunningParticles()
{
if (ForgeHooksClient.createRunningParticles(this)) return;
<< Rest of the methods code here, unaltered >>
}
---
My Hooks method would be added to ForgeHooksClient with alterations (dropping the interface, and inverting boolean logic)
---
public static boolean createRunningParticles(Entity entity)
{
int x = MathHelper.floor(entity.posX);
int y = MathHelper.floor(entity.posY - 0.0625D); // Larger values skip thin blocks, and use the block beneath.
int z = MathHelper.floor(entity.posZ);
BlockPos pos = new BlockPos(x, y, z);
if (entity.getEntityWorld().isAirBlock(pos))
{
pos = new BlockPos(x, MathHelper.floor(entity.posY - 0.2D), z);
}
IBlockState state = entity.getEntityWorld().getBlockState(pos);
return state.getBlock().addRunningEffects(state, entity.getEntityWorld(), pos, entity);
}
---
package shetiphian.asm;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
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 static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
public class ClassTransformer implements IClassTransformer
{
protected final Logger log = LogManager.getLogger("ShetiPhian-ASM");
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if (transformedName.equals("net.minecraft.entity.Entity")) {
return transform(0, basicClass);
}
return basicClass;
}
private byte[] transform(int index, byte[] basicClass)
{
try {
ClassReader reader = new ClassReader(basicClass);
ClassNode node = new ClassNode();
reader.accept(node, 0);
boolean transformed = false;
switch (index) {
case 0:
log.info("Attempting: Injection of Run Particle Override into Entity.createRunningParticles");
transformed = transformEntity(node);
break;
}
if (transformed) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
node.accept(writer);
return writer.toByteArray();
}
} catch (Exception e) {
e.printStackTrace();
}
return basicClass;
}
private boolean transformEntity(ClassNode node)
{
final String ENTITY_PARTICLE = TweakPlugin.runtimeDeobfEnabled ? "func_174808_Z" : "createRunningParticles";
final String ENTITY_PARTICLE_DESC = TweakPlugin.runtimeDeobfEnabled ? "()V" : "()V";
final String HOOK = TweakPlugin.runtimeDeobfEnabled ? "(Lnet/minecraft/entity/Entity;)Z" : "(Lnet/minecraft/entity/Entity;)Z";
log.info("Searching for: Entity.createRunningParticles (" + ENTITY_PARTICLE + ")");
for (MethodNode method : node.methods) {
if (method.name.equals(ENTITY_PARTICLE) && method.desc.equals(ENTITY_PARTICLE_DESC)) {
log.info("Found Method: Entity.createRunningParticles");
AbstractInsnNode targetNode = null;
AbstractInsnNode popNode = null;
for (AbstractInsnNode instruction : method.instructions.toArray()) {
if (instruction.getOpcode() == ALOAD) {
if (targetNode == null && ((VarInsnNode)instruction).var == 0 && instruction.getNext().getOpcode() == GETFIELD) {
targetNode = instruction;
}
}
if (instruction.getOpcode() == RETURN) {
popNode = instruction;
}
}
if (targetNode != null && popNode != null) {
log.info("Injecting: Run Particle Override");
LabelNode newLabelNode = new LabelNode();
InsnList toInsert = new InsnList();
toInsert.add(new VarInsnNode(ALOAD, 0));
toInsert.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Hooks.class), "entityRunParticles", HOOK, false));
toInsert.add(new JumpInsnNode(IFEQ, newLabelNode));
method.instructions.insertBefore(targetNode, toInsert);
method.instructions.insertBefore(popNode, newLabelNode);
return true;
}
log.info("Injection Failed");
}
}
return false;
}
}
package shetiphian.asm;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import shetiphian.core.common.IParticleOverride;
public class Hooks
{
public static boolean entityRunParticles(Entity entity)
{
int x = MathHelper.floor(entity.posX);
int y = MathHelper.floor(entity.posY - 0.0625D);
int z = MathHelper.floor(entity.posZ);
BlockPos pos = new BlockPos(x, y, z);
if (entity.getEntityWorld().isAirBlock(pos)) {
pos = new BlockPos(x, MathHelper.floor(entity.posY - 0.20000000298023224D), z);
}
IBlockState state = entity.getEntityWorld().getBlockState(pos);
if (state.getBlock() instanceof IParticleOverride) {
return !((IParticleOverride)state.getBlock()).overrideRunningEffects(entity.getEntityWorld(), pos, state, entity);
}
return true;
}
}
package shetiphian.asm;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import java.util.Map;
@IFMLLoadingPlugin.Name(value = "ShetiPhian-ASM")
@IFMLLoadingPlugin.MCVersion(value = "1.12")
@IFMLLoadingPlugin.DependsOn(value = "required-after:shetiphiancore")
@IFMLLoadingPlugin.TransformerExclusions(value = "shetiphian.asm.")
@IFMLLoadingPlugin.SortingIndex(value = 1001) // > 1000 = srg names
public class TweakPlugin implements IFMLLoadingPlugin
{
static boolean runtimeDeobfEnabled = false;
@Override
public String[] getASMTransformerClass()
{
return new String[] { "shetiphian.asm.ClassTransformer" };
}
@Override
public String getModContainerClass()
{
return null;
}
@Override
public String getSetupClass()
{
return null;
}
@Override
public void injectData(Map<String, Object> data)
{
runtimeDeobfEnabled = (Boolean)data.get("runtimeDeobfuscationEnabled");
}
@Override
public String getAccessTransformerClass()
{
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment