package net.minecraftforkage.setup_plugin_compat; import java.io.InputStream; import java.io.OutputStream; import java.util.HashSet; import java.util.ListIterator; import java.util.Set; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import net.minecraftforkage.instsetup.AbstractZipFile; import net.minecraftforkage.instsetup.JarTransformer; import net.minecraftforkage.instsetup.PackerContext; public class PlayerAPITransformerTransformer extends JarTransformer { @Override public String getID() { return "MinecraftForkage|Compat|PlayerAPI"; } boolean unknownStrings = false; private String translateString(String s) { switch(s) { // Methods generated by PlayerAPI case "serverPlayerAPI": case "getServerPlayerAPI": case "getEntityPlayerMP": case "getServerPlayerBaseIds": case "getServerPlayerBase": case "dynamic": case "attackEntityPlayerMPEntityWithCurrentItem": case "attackEntityPlayerSPEntityWithCurrentItem": case "<init>": case "clientPlayerAPI": case "getClientPlayerAPI": case "getEntityPlayerSP": case "attackTargetEntityWithCurrentItem": case "getClientPlayerBase": case "getClientPlayerBaseIds": return s; // Misc un-changed strings case "Lph;I": case "Lro;F": case "IIIILadd;": case "Ladd;Z": case "Ladd;ZZ": case "Ladd;I": case "Laji;Z": case "Laji;ZI": case "Lsa;FDD": case "Lyz;Z": case "IIILaji;": return s; // Unknown case "logger": case "pushOutOfBlocks": return s; // Fields case "ySize": return "field_70139_V"; case "yOffset": return "field_70129_M"; case "xpCooldown": return "field_71090_bL"; case "width": return "field_70130_N"; case "wasHungry": return "field_71147_cj"; case "worldObj": return "field_70170_p"; case "velocityChanged": return "field_70133_I"; case "translator": return "field_71148_cg"; case "timeUntilPortal": return "field_71088_bW"; case "ticksExisted": return "field_70173_aa"; case "theItemInWorldManager": return "field_71134_c"; case "teleportDirection": return "field_82152_aq"; case "swingProgressInt": return "field_110158_av"; case "swingProgress": return "field_70733_aJ"; case "stepHeight": return "field_70138_W"; case "speedOnGround": return "field_71108_cd"; case "speedInAir": return "field_71102_ce"; case "sleeping": return "field_71083_bS"; case "serverPosZ": return "field_70116_cv"; case "serverPosY": return "field_70117_cu"; case "serverPosX": return "field_70118_ct"; case "scoreValue": return "field_70744_aE"; case "rotationYawHead": return "field_70759_as"; case "rotationYaw": return "field_70177_z"; case "rotationPitch": return "field_70125_A"; case "ridingEntity": return "field_70154_o"; case "riddenByEntity": return "field_70153_n"; case "renderYawOffset": return "field_70761_aq"; case "renderDistanceWeight": return "field_70155_l"; case "recentlyHit": return "field_70718_bc"; case "randomYawVelocity": return "field_70704_bt"; case "rand": return "field_70146_Z"; case "preventEntitySpawning": return "field_70156_m"; case "prevSwingProgress": return "field_70732_aI"; case "prevRotationYawHead": return "field_70758_at"; case "prevRotationYaw": return "field_70126_B"; case "prevRotationPitch": return "field_70127_C"; case "prevRenderYawOffset": return "field_70760_ar"; case "prevPosZ": return "field_70166_s"; case "prevPosY": return "field_70167_r"; case "prevPosX": return "field_70169_q"; case "prevLimbSwingAmount": return "field_70722_aY"; case "prevHealth": return "field_70735_aL"; case "prevDistanceWalkedModified": return "field_70141_P"; case "prevCameraYaw": return "field_71107_bF"; case "prevCameraPitch": return "field_70727_aS"; case "posZ": return "field_70161_v"; case "posY": return "field_70163_u"; case "posX": return "field_70165_t"; case "portalCounter": return "field_82153_h"; case "playerNetServerHandler": return "field_71135_a"; case "playerLocation": return "field_71081_bT"; case "playerConqueredTheEnd": return "field_71136_j"; case "ping": return "field_71138_i"; case "openContainer": return "field_71070_bA"; case "onGround": return "field_70122_E"; case "noClip": return "field_70145_X"; case "newRotationYaw": return "field_70712_bm"; case "newRotationPitch": return "field_70705_bn"; case "newPosZ": return "field_110152_bk"; case "newPosY": return "field_70710_bk"; case "newPosX": return "field_70709_bj"; case "newPosRotationIncrements": return "field_70716_bi"; case "myEntitySize": return "field_70168_am"; case "moveStrafing": return "field_70702_br"; case "moveForward": return "field_70701_bs"; case "motionZ": return "field_70179_y"; case "motionY": return "field_70181_x"; case "motionX": return "field_70159_w"; case "mcServer": return "field_71133_b"; case "maxHurtTime": return "field_70738_aO"; case "maxHurtResistantTime": return "field_70771_an"; case "managedPosZ": return "field_71132_e"; case "managedPosX": return "field_71131_d"; case "loadedChunks": return "field_71129_f"; case "limbSwingAmount": return "field_70721_aZ"; case "limbSwing": return "field_70754_ba"; case "lastTickPosZ": return "field_70136_U"; case "lastTickPosY": return "field_70137_T"; case "lastTickPosX": return "field_70142_S"; case "lastHealth": return "field_71149_ch"; case "lastFoodLevel": return "field_71146_ci"; case "lastExperience": return "field_71144_ck"; case "lastDamage": return "field_110153_bc"; case "jumpMovementFactor": return "field_70747_aH"; case "isSwingInProgress": return "field_82175_bq"; case "isJumping": return "field_70703_bu"; case "isInWeb": return "field_70134_J"; case "isImmuneToFire": return "field_70178_ae"; case "isDead": return "field_70128_L"; case "isCollidedVertically": return "field_70124_G"; case "isCollidedHorizontally": return "field_70123_F"; case "isCollided": return "field_70132_H"; case "isChangingQuantityOnly": return "field_71137_h"; case "isAirBorne": return "field_70160_al"; case "inventoryContainer": return "field_71069_bz"; case "inventory": return "field_71071_by"; case "inWater": return "field_70171_ac"; case "inPortal": return "field_71087_bX"; case "ignoreFrustumCheck": return "field_70158_ak"; case "hurtTime": return "field_70737_aN"; case "hurtResistantTime": return "field_70172_ad"; case "height": return "field_70158_ak"; case "forceSpawn": return "field_98038_p"; case "foodStats": return "field_71100_bB"; case "flyToggleTimer": return "field_71101_bC"; case "fishEntity": return "field_71104_cf"; case "fireResistance": return "field_70174_ab"; case "fallDistance": return "field_70143_R"; case "experienceTotal": return "field_71067_cb"; case "experienceLevel": return "field_71068_ca"; case "experience": return "field_71106_cc"; case "entityUniqueID": return "field_96093_i"; case "entityCollisionReduction": return "field_70144_Y"; case "entityAge": return "field_70708_bq"; case "distanceWalkedOnStepModified": return "field_82151_R"; case "distanceWalkedModified": return "field_70140_Q"; case "dimension": return "field_71093_bK"; case "destroyedItemsNetCache": return "field_71130_g"; case "deathTime": return "field_70725_aQ"; case "dead": return "field_70729_aU"; case "dataWatcher": return "field_70180_af"; case "currentWindowId": return "field_71139_cq"; case "chunkCoordZ": return "field_70164_aj"; case "chunkCoordY": return "field_70162_ai"; case "chunkCoordX": return "field_70176_ah"; case "chatVisibility": return "field_71143_cn"; case "chatColours": return "field_71140_co"; case "capabilities": return "field_71075_bZ"; case "cameraYaw": return "field_71109_bG"; case "cameraPitch": return "field_70726_aT"; case "boundingBox": return "field_70121_D"; case "attackingPlayer": return "field_70717_bb"; case "attackedAtYaw": return "field_70739_aP"; case "attackTime": return "field_70724_aR"; case "arrowHitTimer": return "field_70720_be"; case "addedToChunk": return "field_70175_ag"; case "horseJumpPower": return "field_110321_bQ"; case "horseJumpPowerCounter": return "field_110320_a"; case "movementInput": return "field_71158_b"; case "prevRenderArmPitch": return "field_71164_i"; case "prevRenderArmYaw": return "field_71163_h"; case "prevTimeInPortal": return "field_71080_cy"; case "renderArmPitch": return "field_71155_g"; case "renderArmYaw": return "field_71154_f"; case "sprintToggleTimer": return "field_71156_d"; case "sprintingTicksLeft": return "sprintingTicksLeft"; case "timeInPortal": return "field_71086_bY"; case "mc": return "field_71159_c"; // Methods case "writeEntityToNBT": return "func_70014_b"; case "wakeUpPlayer": return "func_70999_a"; case "updateRidden": return "func_70098_U"; case "sleepInBedAt": return "func_71018_a"; case "rayTrace": return "func_70614_a"; case "respawnPlayer": return "func_71004_bE"; case "setPositionAndRotation": return "func_70080_a"; case "updatePotionEffects": return "func_70679_bo"; case "updateEntityActionState": return "func_70626_be"; case "swingItem": return "func_71038_i"; case "setSprinting": return "func_70031_b"; case "setSneaking": return "func_70095_a"; case "setPlayerSPHealth": return "func_71150_b"; case "setPosition": return "func_70107_b"; case "setEntityActionState": return "func_110430_a"; case "setDead": return "func_70106_y"; case "readEntityFromNBT": return "func_70037_a"; case "onUpdateEntity": return "func_71127_g"; case "onUpdate": return "func_70071_h_"; case "onStruckByLightning": return "func_70077_a"; case "onKillEntity": return "func_70074_a"; case "onLivingUpdate": return "func_70636_d"; case "onDeath": return "func_70645_a"; case "moveFlying": return "func_70060_a"; case "moveEntityWithHeading": return "func_70612_e"; case "moveEntity": return "func_70091_d"; case "mountEntity": return "func_70078_a"; case "knockBack": return "func_70653_a"; case "jump": return "func_70664_aZ"; case "isSneaking": return "func_70093_af"; case "isSprinting": return "func_70051_ag"; case "isPlayerSleeping": return "func_70608_bn"; case "isOnLadder": return "func_70617_f_"; case "isInsideOfMaterial": return "func_70055_a"; case "isInWater": return "func_70090_H"; case "isEntityInsideOpaqueBlock": return "func_70094_T"; case "heal": return "func_70691_i"; case "handleWaterMovement": return "func_70072_I"; case "handleLavaMovement": return "func_70058_J"; case "getSleepTimer": return "func_71060_bI"; case "getItemIcon": return "func_70620_b"; case "getHurtSound": return "func_70621_aR"; case "getFOVMultiplier": return "func_71151_f"; case "getEyeHeight": return "func_70047_e"; case "getBrightness": return "func_70013_c"; case "getDistanceSqToEntity": return "func_70068_e"; case "getDistanceSq": return "func_70092_e"; case "getBrightnessForRender": return "func_70070_b"; case "getBreakSpeed": return s; // Forge case "getCurrentPlayerStrVsBlockForge": return s; // PlayerAPI? case "getCurrentPlayerStrVsBlock": return "func_146096_a"; case "getBedOrientationInDegrees": return "func_71051_bG"; case "getAIMoveSpeed": return "func_70689_ay"; case "fall": return "func_70069_a"; case "dropPlayerItemWithRandomChoice": return "func_71019_a"; case "dropPlayerItem": return "func_146097_a"; case "playStepSound": return "func_145780_a"; case "dropOneItem": return "func_71040_bB"; case "displayGUIWorkbench": return "func_71058_b"; case "displayGUIFurnace": return "func_146101_a"; case "displayGUIEnchantment": return "func_71002_c"; case "displayGUIDispenser": return "func_146102_a"; case "displayGUIChest": return "func_71007_a"; case "displayGUIBrewingStand": return "func_146098_a"; case "displayGUIEditSign": return "func_146100_a"; case "damageEntity": return "func_70665_d"; case "closeScreen": return "func_71053_j"; case "clonePlayer": return "func_71049_a"; case "canTriggerWalking": return "func_70041_e_"; case "canPlayerEdit": return "func_82247_a"; case "canHarvestBlock": return "func_146099_a"; case "canBreatheUnderwater": return "func_70648_aU"; case "attackEntityFrom": return "func_70097_a"; case "addStat": return "func_71064_a"; case "addMovementStat": return "func_71000_j"; case "addExperienceLevel": return "func_82242_a"; case "addExperience": return "func_71023_q"; case "addExhaustion": return "func_71020_j"; default: if(s.length() <= 3) return s; // ignore obfuscated names (we make Player API run in deobfuscated mode) if(s.contains("/") || (s.startsWith("(") && s.contains(")")) || (s.startsWith("L") && s.endsWith(";"))) return s; // ignore class names and method descriptors if((s.startsWith("get") || s.startsWith("set")) && s.endsWith("Field")) return s; // ignore generated getter and setter names if(s.startsWith("field_") || s.startsWith("func_")) return s; // already an SRG name if(s.startsWith("real") || s.startsWith("local") || s.startsWith("super")) return s; // added by PlayerAPI System.err.println("Unknown string "+s); unknownStrings = true; return s; } } private void forceDeObfuscatedMode(MethodNode mn) { ListIterator<AbstractInsnNode> it = mn.instructions.iterator(); while(it.hasNext()) { AbstractInsnNode ain = it.next(); if(ain.getOpcode() == Opcodes.GETFIELD && ((FieldInsnNode)ain).name.equals("isObfuscated")) { // instead of reading the isObfuscated field, always read 'false' it.set(new InsnNode(Opcodes.POP)); it.add(new InsnNode(Opcodes.ICONST_0)); } } } private void translateAllStrings(MethodNode mn) { ListIterator<AbstractInsnNode> it = mn.instructions.iterator(); while(it.hasNext()) { AbstractInsnNode ain = it.next(); if(ain.getOpcode() == Opcodes.LDC) { LdcInsnNode ldc = (LdcInsnNode)ain; if(ldc.cst instanceof String) { ldc.cst = translateString((String)ldc.cst); } } } } private void translateSomeStrings_VisitEnd(MethodNode mn) { ListIterator<AbstractInsnNode> it = mn.instructions.iterator(); while(it.hasNext()) { AbstractInsnNode ain = it.next(); if(ain.getOpcode() == Opcodes.LDC) { LdcInsnNode ldc = (LdcInsnNode)ain; if(ldc.cst instanceof String) { String translated = translateString((String)ldc.cst); if(translated.equals(ldc.cst)) continue; // no translation // See if there's a PlayerAPI class name between this and the last visitMethod call. // If true, this string is *probably* the name of a method that exists on IServerPlayerAPI/IClientPlayerAPI boolean foundPlayerAPIClassString = false; for(AbstractInsnNode ain2 = ain; ain2 != null; ain2 = ain2.getPrevious()) { if(ain2.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain2).name.equals("visitMethod")) { break; } if(ain2.getOpcode() == Opcodes.LDC && ((LdcInsnNode)ain2).cst instanceof String && ((String)((LdcInsnNode)ain2).cst).startsWith("api/player/")) { foundPlayerAPIClassString = true; break; } } String nextCall = null; for(; ain != null; ain = ain.getNext()) { if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL) { if(((MethodInsnNode)ain).owner.startsWith("java")) continue; nextCall = ((MethodInsnNode)ain).name; break; } } if(nextCall == null) throw new RuntimeException("No invokevirtual after "+ldc+" ("+ldc.cst+") in "+mn.name); boolean doTranslate; if(nextCall.equals("visitMethod") || nextCall.equals("visitFieldInsn")) doTranslate = true; else if(nextCall.equals("visitMethodInsn")) doTranslate = !foundPlayerAPIClassString; else throw new RuntimeException("next call is "+nextCall+"?"); if(doTranslate) ldc.cst = translated; } } } } @Override public void transform(AbstractZipFile zipFile, PackerContext context) throws Exception { int numSeen = 0; for(String transformerPath : new String[] { "api/player/client/ClientPlayerClassVisitor.class", "api/player/server/ServerPlayerClassVisitor.class" }) { if(!zipFile.doesPathExist(transformerPath)) continue; ClassNode cn = new ClassNode(); ClassWriter cw = new ClassWriter(0); try(InputStream in = zipFile.read(transformerPath)) { new ClassReader(in).accept(cn, 0); } for(MethodNode mn : cn.methods) { forceDeObfuscatedMode(mn); if(mn.name.equals("visitMethod")) translateAllStrings(mn); if(mn.name.equals("visitEnd")) translateSomeStrings_VisitEnd(mn); } cn.accept(cw); try(OutputStream out = zipFile.write(transformerPath)) { out.write(cw.toByteArray()); } } if(numSeen != 0 && numSeen != 2) throw new RuntimeException("Weird installation of Player API? Found "+numSeen+" transformer classes, expected 2"); if(unknownStrings) throw new RuntimeException("found unknown strings"); } }