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");
}
}