Hey fellas! So I've been working on a mod for my Minecraft server that adds "admin tools" ( that's code for "trolling commands" ( : ) It's been pretty smooth so far: I've heard them scream as they were chased by every enemy in the entire world, I've watched as they mysteriously turned into potatoes, and I've even gotten to hear them say strange things like "Why th are my pants on my head?" However, I've lately started running into an issue that I can't quite seem to get through... Like at all! It started when I thought it would be a really cool idea to implement a command that makes EVERYTHING invisible for a selected player. You know, the usual everyday kinda' tool needed for proper server administration. But I can't just set all the entities to be invisible with the setInvisible(boolean value) kind of deal, because that would mean that if you had either a hacked client or the entity had some kind of equipment, then you could still see it. I wanted it so that the client that I chose didn't even know the entity existed. So, I initially started by just trying to intercept packets. However, this only worked for vanilla entities, like zombies and basic items. The reason? Because forge is really annoying and decides that it's not going to spawn mod entities through the default channel and is instead going to use an entirely different channel, one that I can't conveniently connect a player entity to through a CustomPacketHandler because this channel remains static throughout the server's lifespan. So I though, jeez, well why don't I just add another CustomPacketHandler to this channel and see how it goes. So I did my first test and tried to prevent an entity spawn packet from being written to the channel. And can you guess what happened? Probably not, because it wasn't a NoClassDefFound exception. Instead, it was a NullReferenceException that lead to an error that said something like, "Don't know how to spawn in this entity." As it turns out, when the packet for spawning an entity is being created, it relies on trying to write the packet to this fml channel and then for some reason it just reads the packet from the fml channel before trying to send it again. So that means if you prevent it from being written then there's nothing that can be read from the channel, so the spawn packet ends up being null. So I though, damn, this is really stupid. So I searched around a bit and found a function called isVisibleTo, which is basically just called to check whether a player can or can't see an entity before it decides to make the packet to send to the player. Then I got an idea: what if I just hijack this function and add some code at the start to make it return false if the player has had this "invisible enemy" command ran on them. So I looked around a little more, and finally, I found out that if you make your mod a plugin, then you can access the transform method of the IClassTransformer interface. Basically, it's like the harmony transpiler patch, but with java! So I got to work. Now, understand that everything before this point it basically just pretext to give you fellas some good context. From this point on, this is where my struggles are going to start to begin. I started off with writing my hijacking function. It was a static public function with one argument of the type EntityPlayerMP that returned a boolean value. I named this function "canPlayerSee". Now, on the AsmTranspiler end, I had gotten the class/method descriptors and function names from the obfuscated mappings and pulled apart the isVisibleTo function's disassembly. From here, I put together some quick code that injected at the start of the isVisibleTo function the following java assembly codes: aload_1 //This loads the first, and only argument (excluding the this argument) of the isVisibleTo function invokestatic //canPlayerSee ifne //label_0 iconst_0 //This should load the 0 integer ireturn //This should return the number at the top of the stack, zero in this case label_0 So I loaded up the server, and nothing bad happened. I run the command on myself to make everything invisible and it works perfectly! So at this point, I'm thinking that this would be a pretty powerful tool if I could load an instance of the Entity that the isVisibleTo function concerns onto the stack and pass is as an argument to the canPlayerSee function for better use. This would let me implement a propper vanish command and a command that would let me turn players of my choice into a creeper that the player in question cannot see. So, I modified the assembly code again. I changed it to this instead: (Note, I did update the canPlayerSee function to accept the new Entity arg at the end) aload_1 //This loads the first, and only argument (excluding the this argument) of the isVisibleTo function aload_0 //Loads the this arg to the stack getfield //EntityTrackerEntry.trackedEntity invokestatic //canPlayerSee ifne //label_0 iconst_0 //This should load the 0 integer ireturn //This should return the number at the top of the stack, zero in this case label_0 So I launched up the server again... And it crashes: NoClassDefFoundException. This is the error, I am currently getting, and I have tried almost everything I could think of to fix it. Hell, I even tried praying, which almost always works! None of those things I tried are of particular note here, but what is of particular note is the fact that it seems that the crashing will happen only when I make the canPlayerSee function two arguments and change the code to reflect loading the proper arguments. Now, I'm going to send some source code, so just be ready: my chunking skills are horrible and my coding "elegancy" might make you really want to puke. For that, I am sorry. Also, since it's like 3:00 AM rn, I'm just going to try and send images instead of providing text. I know this will make it a bit harder to help, so I am sorry for that too, but I'm really tired. Nvm, I guess I'm sending text: This is my entire class for modifying the asm code: public class AsmTransformer implements IClassTransformer {
@SuppressWarnings("deprecation")
private static Logger logger = FMLLog.getLogger();
public byte[] transform(String name, String transformedName, byte[] bytes) {
if (transformedName.startsWith("net.minecraft.entity.EntityTrackerEntry")) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode();
reader.accept((ClassVisitor)node, 0);
if (analyzeClass(node)) {
ClassWriter writer = new ClassWriter(3);
node.accept(writer);
bytes = writer.toByteArray();
}
}
return bytes;
}
private boolean analyzeClass(ClassNode node) {
boolean rval = false;
for (MethodNode method : node.methods) {
logger.info(method.name);
logger.info(method.desc);
if (method.name.equals("isVisibleTo") || (method.name.equals("c") && method.desc.equals("(Loq;)Z"))) {
try {
rval = hijackCanSeeFunc(method);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
if(!rval)
logger.info("COULD NOT FIND METHOD!");
return rval;
}
private boolean hijackCanSeeFunc(MethodNode node) throws NoSuchMethodException, SecurityException {
InsnList insnList = node.instructions;
logger.info("Hackin' it...");
Method meth = CustomHandler.class.getMethod("canPlayerSee", Entity.class, EntityPlayerMP.class);
String methDesc = Type.getMethodDescriptor(meth);
logger.info(methDesc);
InsnList patch = new InsnList();
LabelNode ln = new LabelNode(new Label());
patch.add((AbstractInsnNode) new VarInsnNode(Opcodes.ALOAD, 0));
patch.add((AbstractInsnNode) new FieldInsnNode(Opcodes.GETFIELD, Type.getInternalName(EntityTrackerEntry.class),
"d", "Lvg;"));
patch.add((AbstractInsnNode) new VarInsnNode(Opcodes.ALOAD, 1));
patch.add((AbstractInsnNode) new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(CustomHandler.class),
"canPlayerSee", methDesc, false));
patch.add((AbstractInsnNode) new JumpInsnNode(Opcodes.IFNE, ln));
patch.add((AbstractInsnNode) new InsnNode(Opcodes.ICONST_0));
patch.add((AbstractInsnNode) new InsnNode(Opcodes.IRETURN));
patch.add((AbstractInsnNode) ln);
insnList.insertBefore(insnList.getFirst(), patch);
return true;
}
}Of particular note here is that last hijack function. The second to last chunk contains the code that inserts my own instructions. This is the custom function I am injecting at the start of the isVisibleTo function: public static boolean canPlayerSee(Entity instance, EntityPlayerMP target) {
if (playerInvisTrollList.contains(target.getName())) {
return false;
} else if (instance instanceof EntityPlayerMP
&& playerVanishList.get(((EntityPlayerMP) instance).getName()) != null) {
return false;
} else if (findListElem(Common.creepers,
index -> index.creep != null
&& index.creep.getEntityId() == instance.getEntityId()) != null
&& findListElem(Common.creepers, index -> index.name.contentEquals(target.getName())) != null) {
return false;
}
return true;
}I know, it's not chunked like at all. My bad. Also, the findListElem function is basically just the removeIf function for lists, except for instead of removing the element when it matches the predicate, it will just return it instead. It's weird how lists don't already have this function (as far as I know). Here is the stacktrace: java.lang.NoClassDefFoundError: net/minecraft/entity/EntityTrackerEntry
at net.minecraft.entity.EntityTracker.func_72785_a(EntityTracker.java:252) ~[ol.class:?]
at net.minecraft.entity.EntityTracker.func_72786_a(EntityTracker.java:157) ~[ol.class:?]
at net.minecraft.world.ServerWorldEventHandler.func_72703_a(ServerWorldEventHandler.java:37) ~[op.class:?]
at net.minecraft.world.World.func_72923_a(World.java:1222) ~[amu.class:?]
at net.minecraft.world.WorldServer.func_72923_a(WorldServer.java:1112) ~[oo.class:?]
at net.minecraft.world.WorldServer.func_175650_b(WorldServer.java:1068) ~[oo.class:?]
at net.minecraft.world.chunk.Chunk.func_76631_c(Chunk.java:860) ~[axw.class:?]
at net.minecraftforge.common.chunkio.ChunkIOProvider.syncCallback(ChunkIOProvider.java:109) ~[ChunkIOProvider.class:?]
at net.minecraftforge.common.chunkio.ChunkIOExecutor.syncChunkLoad(ChunkIOExecutor.java:94) ~[ChunkIOExecutor.class:?]
at net.minecraft.world.gen.ChunkProviderServer.loadChunk(ChunkProviderServer.java:118) ~[on.class:?]
at net.minecraft.world.gen.ChunkProviderServer.func_186028_c(ChunkProviderServer.java:89) ~[on.class:?]
at net.minecraft.world.gen.ChunkProviderServer.func_186025_d(ChunkProviderServer.java:135) ~[on.class:?]
at net.minecraft.server.MinecraftServer.func_71222_d(MinecraftServer.java:344) ~[MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer.func_71247_a(MinecraftServer.java:314) ~[MinecraftServer.class:?]
at net.minecraft.server.dedicated.DedicatedServer.func_71197_b(DedicatedServer.java:270) ~[nz.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:486) [MinecraftServer.class:?]
at java.lang.Thread.run(Thread.java:750) [?:1.8.0_451]Not much I gotta' say about this. Here, I even got the disassembled code for the isVisibleTo function in case it helps: public boolean isVisibleTo(net.minecraft.entity.player.EntityPlayerMP);
descriptor: (Lnet/minecraft/entity/player/EntityPlayerMP;)Z
flags: ACC_PUBLIC
Code:
stack=6, locals=7, args_size=2
0: aload_1
1: getfield #148 // Field net/minecraft/entity/player/EntityPlayerMP.posX:D
4: aload_0
5: getfield #13 // Field encodedPosX:J
8: l2d
9: ldc2_w #149 // double 4096.0d
12: ddiv
13: dsub
14: dstore_2
15: aload_1
16: getfield #151 // Field net/minecraft/entity/player/EntityPlayerMP.posZ:D
19: aload_0
20: getfield #17 // Field encodedPosZ:J
23: l2d
24: ldc2_w #149 // double 4096.0d
27: ddiv
28: dsub
29: dstore 4
31: aload_0
32: getfield #7 // Field range:I
35: aload_0
36: getfield #8 // Field maxRange:I
39: invokestatic #152 // Method java/lang/Math.min:(II)I
42: istore 6
44: dload_2
45: iload 6
47: ineg
48: i2d
49: dcmpl
50: iflt 95
53: dload_2
54: iload 6
56: i2d
57: dcmpg
58: ifgt 95
61: dload 4
63: iload 6
65: ineg
66: i2d
67: dcmpl
68: iflt 95
71: dload 4
73: iload 6
75: i2d
76: dcmpg
77: ifgt 95
80: aload_0
81: getfield #6 // Field trackedEntity:Lnet/minecraft/entity/Entity;
84: aload_1
85: invokevirtual #153 // Method net/minecraft/entity/Entity.isSpectatedByPlayer:(Lnet/minecraft/entity/player/EntityPlayerMP;)Z
88: ifeq 95
91: iconst_1
92: goto 96
95: iconst_0
96: ireturn
LineNumberTable:
line 475: 0
line 476: 15
line 477: 31
line 478: 44
LocalVariableTable:
Start Length Slot Name Signature
0 97 0 this Lnet/minecraft/entity/EntityTrackerEntry;
0 97 1 playerMP Lnet/minecraft/entity/player/EntityPlayerMP;
15 82 2 d0 D
31 66 4 d1 D
44 53 6 i I
StackMapTable: number_of_entries = 2
frame_type = 254 /* append */
offset_delta = 95
locals = [ double, double, int ]
frame_type = 64 /* same_locals_1_stack_item */
stack = [ int ] This is pretty much everything I can think to provide. I'm not sure what else I can do for you fellas rn, but I hope this can get somebody somewhere. Even if you aren't confident you know exactly what's going on, if you think you have some kind of idea, please don't be shy about replyin'. I would appreciate ANY amount of help here with this, no matter how menial or small it is. Thank you.
By
Jackson Perr · Posted 9 hours ago 9 hr
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.