package org.dynmap.bukkit.helper; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.entity.Player; import org.dynmap.Log; import com.google.common.collect.ForwardingMultimap; import com.google.common.collect.Iterables; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; /** * Helper for isolation of bukkit version specific issues */ public abstract class BukkitVersionHelperGeneric extends BukkitVersionHelper { private String obc_package; // Package used for org.bukkit.craftbukkit protected String nms_package; // Package used for net.minecraft.server private boolean failed; protected static final Object[] nullargs = new Object[0]; protected static final Class[] nulltypes = new Class[0]; private static final Map nullmap = Collections.emptyMap(); /** CraftChunkSnapshot */ protected Class craftchunksnapshot; private Field ccss_biome; /** CraftChunk */ private Class craftchunk; private Method cc_gethandle; /** CraftWorld */ private Class craftworld; private Method cw_gethandle; /** BiomeBase related helpers */ protected Class biomestorage; protected Field biomestoragebase; protected Class biomebase; protected Class biomebasearray; protected Field biomebaselist; protected Field biomebasetemp; protected Field biomebasehumi; protected Method biomebasetempfunc; protected Method biomebasehumifunc; protected Field biomebaseidstring; protected Field biomebaseid; /** n.m.s.World */ protected Class nmsworld; protected Class chunkprovserver; protected Class longhashset; protected Field nmsw_chunkproviderserver; protected Field cps_unloadqueue; protected boolean cps_unloadqueue_isSet; protected Method lhs_containskey; /** n.m.s.Chunk */ protected Class nmschunk; protected Field nmsc_tileentities; protected Field nmsc_inhabitedticks; /** nbt classes */ protected Class nbttagcompound; protected Class nbttagbyte; protected Class nbttagshort; protected Class nbttagint; protected Class nbttaglong; protected Class nbttagfloat; protected Class nbttagdouble; protected Class nbttagbytearray; protected Class nbttagstring; protected Class nbttagintarray; protected Method compound_get; protected Field nbttagbyte_val; protected Field nbttagshort_val; protected Field nbttagint_val; protected Field nbttaglong_val; protected Field nbttagfloat_val; protected Field nbttagdouble_val; protected Field nbttagbytearray_val; protected Field nbttagstring_val; protected Field nbttagintarray_val; /** Tile entity */ protected Class nms_tileentity; protected Method nmst_readnbt; protected Field nmst_x; protected Field nmst_y; protected Field nmst_z; protected Method nmst_getposition; /** BlockPosition */ protected Class nms_blockposition; protected Method nmsbp_getx; protected Method nmsbp_gety; protected Method nmsbp_getz; /** Server */ protected Method server_getonlineplayers; /** Player */ protected Method player_gethealth; // CraftPlayer private Class obc_craftplayer; private Method obcplayer_getprofile; // GameProfile private Class cma_gameprofile; private Method cmaprofile_getproperties; // Property private Class cma_property; private Method cmaproperty_getvalue; protected BukkitVersionHelperGeneric() { failed = false; /* Look up base classname for bukkit server - tells us OBC package */ obc_package = Bukkit.getServer().getClass().getPackage().getName(); /* Get NMS package */ nms_package = getNMSPackage(); if(nms_package == null) { failed = true; } /* Craftworld fields */ craftworld = getOBCClass("org.bukkit.craftbukkit.CraftWorld"); cw_gethandle = getMethod(craftworld, new String[] { "getHandle" }, new Class[0]); /* CraftChunkSnapshot */ craftchunksnapshot = getOBCClass("org.bukkit.craftbukkit.CraftChunkSnapshot"); biomebasearray = getNMSClass("[Lnet.minecraft.server.BiomeBase;"); ccss_biome = getPrivateFieldNoFail(craftchunksnapshot, new String[] { "biome" }, biomebasearray); if(ccss_biome == null) { biomestorage = getNMSClass("net.minecraft.server.BiomeStorage"); biomestoragebase = getPrivateField(biomestorage, new String[] { "h", "g", "f" }, biomebasearray); ccss_biome = getPrivateField(craftchunksnapshot, new String[] { "biome" }, biomestorage); } /* CraftChunk */ craftchunk = getOBCClass("org.bukkit.craftbukkit.CraftChunk"); cc_gethandle = getMethod(craftchunk, new String[] { "getHandle" }, new Class[0]); /** Server */ server_getonlineplayers = getMethod(Server.class, new String[] { "getOnlinePlayers" }, new Class[0]); /** Player */ player_gethealth = getMethod(Player.class, new String[] { "getHealth" }, new Class[0]); // CraftPlayer obc_craftplayer = getOBCClass("org.bukkit.craftbukkit.entity.CraftPlayer"); obcplayer_getprofile = getMethod(obc_craftplayer, new String[] { "getProfile" }, new Class[0]); // GameProfile cma_gameprofile = getOBCClass("com.mojang.authlib.GameProfile"); cmaprofile_getproperties = getMethod(cma_gameprofile, new String[] { "getProperties" }, new Class[0]); // Property cma_property = getOBCClass("com.mojang.authlib.properties.Property"); cmaproperty_getvalue = getMethod(cma_property, new String[] { "getValue" }, new Class[0]); /* Get NMS classes and fields */ if(!failed) loadNMS(); if(failed) throw new IllegalArgumentException("Error initializing dynmap - bukkit version incompatible!"); } protected abstract void loadNMS(); protected abstract String getNMSPackage(); protected Class getOBCClass(String classname) { return getClassByName(classname, "org.bukkit.craftbukkit", obc_package, false); } protected Class getOBCClassNoFail(String classname) { return getClassByName(classname, "org.bukkit.craftbukkit", obc_package, true); } protected Class getNMSClass(String classname) { return getClassByName(classname, "net.minecraft.server", nms_package, false); } protected Class getNMSClassNoFail(String classname) { return getClassByName(classname, "net.minecraft.server", nms_package, true); } protected Class getClassByName(String classname, String base, String mapping, boolean nofail) { String n = classname; int idx = classname.indexOf(base); if(idx >= 0) { n = classname.substring(0, idx) + mapping + classname.substring(idx + base.length()); } try { return Class.forName(n); } catch (ClassNotFoundException cnfx) { try { return Class.forName(classname); } catch (ClassNotFoundException cnfx2) { if(!nofail) { Log.severe("Cannot find " + classname); failed = true; } return null; } } } /** * Get field */ protected Field getField(Class cls, String[] ids, Class type) { return getField(cls, ids, type, false); } protected Field getFieldNoFail(Class cls, String[] ids, Class type) { return getField(cls, ids, type, true); } /** * Get field */ private Field getField(Class cls, String[] ids, Class type, boolean nofail) { if((cls == null) || (type == null)) return null; for(String id : ids) { try { Field f = cls.getField(id); if(f.getType().isAssignableFrom(type)) { return f; } } catch (NoSuchFieldException nsfx) { } } if(!nofail) { Log.severe("Unable to find field " + ids[0] + " for " + cls.getName()); failed = true; } return null; } /** * Get private field */ protected Field getPrivateFieldNoFail(Class cls, String[] ids, Class type) { return getPrivateField(cls, ids, type, true); } /** * Get private field */ protected Field getPrivateField(Class cls, String[] ids, Class type) { return getPrivateField(cls, ids, type, false); } /** * Get private field */ protected Field getPrivateField(Class cls, String[] ids, Class type, boolean nofail) { if((cls == null) || (type == null)) return null; for(String id : ids) { try { Field f = cls.getDeclaredField(id); if(f.getType().isAssignableFrom(type)) { f.setAccessible(true); return f; } } catch (NoSuchFieldException nsfx) { } } if (!nofail) { Log.severe("Unable to find field " + ids[0] + " for " + cls.getName()); failed = true; } return null; } protected Object getFieldValue(Object obj, Field field, Object def) { if((obj != null) && (field != null)) { try { return field.get(obj); } catch (IllegalArgumentException e) { Log.warning(String.format("IllegalArgExc(%s,%s)", obj.toString(), field.toString())); } catch (IllegalAccessException e) { Log.warning(String.format("IllegalAccessExc(%s,%s)", obj.toString(), field.toString())); } } else { Log.warning(String.format("NullArg(%s,%s)", (obj != null)?obj.toString():"null", (field != null)?field.toString():"null")); } return def; } /** * Get method */ protected Method getMethod(Class cls, String[] ids, Class[] args) { if(cls == null) return null; for(String id : ids) { try { return cls.getMethod(id, args); } catch (SecurityException e) { } catch (NoSuchMethodException e) { } } Log.severe("Unable to find method " + ids[0] + " for " + cls.getName()); failed = true; return null; } protected Method getMethodNoFail(Class cls, String[] ids, Class[] args) { if(cls == null) return null; for(String id : ids) { try { return cls.getMethod(id, args); } catch (SecurityException e) { } catch (NoSuchMethodException e) { } } return null; } protected Object callMethod(Object obj, Method meth, Object[] args, Object def) { if((obj == null) || (meth == null)) { return def; } try { return meth.invoke(obj, args); } catch (IllegalArgumentException iax) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } return def; } /** * Get list of defined biomebase objects */ public Object[] getBiomeBaseList() { Object baselist = getFieldValue(biomebase, biomebaselist, new Object[0]); if(biomestoragebase != null) baselist = getFieldValue(baselist, biomestoragebase, new Object[0]); return (Object[])baselist; } /** Get temperature from biomebase */ public float getBiomeBaseTemperature(Object bb) { if (biomebasetempfunc != null) { return (Float) callMethod(bb, biomebasetempfunc, new Object[0], 0.5f); } else { return (Float) getFieldValue(bb, biomebasetemp, 0.5F); } } /** Get humidity from biomebase */ public float getBiomeBaseHumidity(Object bb) { if (biomebasehumifunc != null) { return (Float) callMethod(bb, biomebasehumifunc, new Object[0], 0.5f); } else { return (Float) getFieldValue(bb, biomebasehumi, 0.5F); } } /** Get ID string from biomebase */ public String getBiomeBaseIDString(Object bb) { return (String) getFieldValue(bb, biomebaseidstring, null); } /** Get ID from biomebase */ public int getBiomeBaseID(Object bb) { return (Integer) getFieldValue(bb, biomebaseid, -1); } /* Get net.minecraft.server.world for given world */ protected Object getNMSWorld(World w) { return callMethod(w, cw_gethandle, nullargs, null); } /* Get unload queue for given NMS world */ public Object getUnloadQueue(World world) { Object cps = getFieldValue(getNMSWorld(world), nmsw_chunkproviderserver, null); // Get chunkproviderserver if ((cps != null) && (cps_unloadqueue != null)) { return getFieldValue(cps, cps_unloadqueue, null); } return null; } /* For testing unload queue for presence of givne chunk */ public boolean isInUnloadQueue(Object unloadqueue, int x, int z) { if(unloadqueue != null) { if (cps_unloadqueue_isSet) return ((Set) unloadqueue).contains(Long.valueOf((long)x & 0xFFFFFFFF | ((long)z & 0xFFFFFFFF) << 32)); return (Boolean)callMethod(unloadqueue, lhs_containskey, new Object[] { x, z }, true); } return true; } public Object[] getBiomeBaseFromSnapshot(ChunkSnapshot css) { return (Object[])getFieldValue(css, ccss_biome, null); } // public boolean isCraftChunkSnapshot(ChunkSnapshot css) { // if(craftchunksnapshot != null) { // return craftchunksnapshot.isAssignableFrom(css.getClass()); // } // return false; // } /** * Get inhabited ticks count from chunk */ private static final Long zero = Long.valueOf(0); public long getInhabitedTicks(Chunk c) { if (nmsc_inhabitedticks == null) { return 0; } Object omsc = callMethod(c, cc_gethandle, nullargs, null); if(omsc != null) { return (Long)getFieldValue(omsc, nmsc_inhabitedticks, zero); } return 0; } /** Get tile entities map from chunk */ public Map getTileEntitiesForChunk(Chunk c) { Object omsc = callMethod(c, cc_gethandle, nullargs, null); if(omsc != null) { return (Map)getFieldValue(omsc, nmsc_tileentities, nullmap); } return nullmap; } /** * Get X coordinate of tile entity */ public int getTileEntityX(Object te) { if (nmst_getposition == null) { return (Integer)getFieldValue(te, nmst_x, 0); } else { Object pos = callMethod(te, nmst_getposition, nullargs, null); return (Integer) callMethod(pos, nmsbp_getx, nullargs, null); } } /** * Get Y coordinate of tile entity */ public int getTileEntityY(Object te) { if (nmst_getposition == null) { return (Integer)getFieldValue(te, nmst_y, 0); } else { Object pos = callMethod(te, nmst_getposition, nullargs, null); return (Integer) callMethod(pos, nmsbp_gety, nullargs, null); } } /** * Get Z coordinate of tile entity */ public int getTileEntityZ(Object te) { if (nmst_getposition == null) { return (Integer)getFieldValue(te, nmst_z, 0); } else { Object pos = callMethod(te, nmst_getposition, nullargs, null); return (Integer) callMethod(pos, nmsbp_getz, nullargs, null); } } /** * Read tile entity NBT */ @Override public Object readTileEntityNBT(Object te, org.bukkit.World w) { if(nbttagcompound == null) return null; Object nbt = null; try { nbt = nbttagcompound.newInstance(); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } if(nbt != null) { callMethod(te, nmst_readnbt, new Object[] { nbt }, null); } return nbt; } /** * Get field value from NBT compound */ public Object getFieldValue(Object nbt, String field) { Object val = callMethod(nbt, compound_get, new Object[] { field }, null); if(val == null) return null; Class valcls = val.getClass(); if(valcls.equals(nbttagbyte)) { return getFieldValue(val, nbttagbyte_val, null); } else if(valcls.equals(nbttagshort)) { return getFieldValue(val, nbttagshort_val, null); } else if(valcls.equals(nbttagint)) { return getFieldValue(val, nbttagint_val, null); } else if(valcls.equals(nbttaglong)) { return getFieldValue(val, nbttaglong_val, null); } else if(valcls.equals(nbttagfloat)) { return getFieldValue(val, nbttagfloat_val, null); } else if(valcls.equals(nbttagdouble)) { return getFieldValue(val, nbttagdouble_val, null); } else if(valcls.equals(nbttagbytearray)) { return getFieldValue(val, nbttagbytearray_val, null); } else if(valcls.equals(nbttagstring)) { return getFieldValue(val, nbttagstring_val, null); } else if(valcls.equals(nbttagintarray)) { return getFieldValue(val, nbttagintarray_val, null); } return null; } /** * Get list of online players */ public Player[] getOnlinePlayers() { Object players = callMethod(Bukkit.getServer(), server_getonlineplayers, nullargs, null); if (players instanceof Player[]) { /* Pre 1.7.10 */ return (Player[]) players; } else { @SuppressWarnings("unchecked") Collection p = (Collection) players; return p.toArray(new Player[0]); } } /** * Get player health */ @Override public double getHealth(Player p) { Object health = callMethod(p, player_gethealth, nullargs, null); if (health instanceof Integer) { return (Integer) health; } else { return ((Double) health).intValue(); } } private static final Gson gson = new GsonBuilder().create(); public class TexturesPayload { public long timestamp; public String profileId; public String profileName; public boolean isPublic; public Map textures; } public class ProfileTexture { public String url; } /** * Get skin URL for player * @param player */ public String getSkinURL(Player player) { String url = null; Object profile = callMethod(player, obcplayer_getprofile, nullargs, null); if (profile != null) { Object propmap = callMethod(profile, cmaprofile_getproperties, nullargs, null); if ((propmap != null) && (propmap instanceof ForwardingMultimap)) { ForwardingMultimap fmm = (ForwardingMultimap) propmap; Collection txt = fmm.get("textures"); Object textureProperty = Iterables.getFirst(fmm.get("textures"), null); if (textureProperty != null) { String val = (String) callMethod(textureProperty, cmaproperty_getvalue, nullargs, null); if (val != null) { TexturesPayload result = null; try { String json = new String(Base64.getDecoder().decode(val), StandardCharsets.UTF_8); result = gson.fromJson(json, TexturesPayload.class); } catch (JsonParseException e) { } catch (IllegalArgumentException x) { Log.warning("Malformed response from skin URL check: " + val); } if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { url = result.textures.get("SKIN").url; } } } } } return url; } }