Last active
August 29, 2015 14:18
-
-
Save Cybermaxke/26be8f87fc2d6f21d3b6 to your computer and use it in GitHub Desktop.
Modify the title of the inventory that is send to the client.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title; | |
import org.bukkit.entity.Player; | |
public interface TitleProvider { | |
Title provideFor(Player player); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title.internal; | |
import java.util.Set; | |
import java.util.Map.Entry; | |
import org.bukkit.ChatColor; | |
import org.json.simple.JSONArray; | |
import org.json.simple.JSONObject; | |
import org.json.simple.parser.JSONParser; | |
import org.json.simple.parser.ParseException; | |
import com.google.common.collect.BiMap; | |
import com.google.common.collect.HashBiMap; | |
import com.google.common.collect.Sets; | |
public class JsonUtils { | |
private static final BiMap<String, ChatColor> lookupColors = HashBiMap.create(); | |
private static final BiMap<String, ChatColor> lookupStyles = HashBiMap.create(); | |
static { | |
lookupColors.put("white", ChatColor.WHITE); | |
lookupColors.put("black", ChatColor.BLACK); | |
lookupColors.put("yellow", ChatColor.YELLOW); | |
lookupColors.put("gold", ChatColor.GOLD); | |
lookupColors.put("aqua", ChatColor.AQUA); | |
lookupColors.put("dark_aqua", ChatColor.DARK_AQUA); | |
lookupColors.put("blue", ChatColor.BLUE); | |
lookupColors.put("dark_blue", ChatColor.DARK_BLUE); | |
lookupColors.put("light_purple", ChatColor.LIGHT_PURPLE); | |
lookupColors.put("dark_purple", ChatColor.DARK_PURPLE); | |
lookupColors.put("red", ChatColor.RED); | |
lookupColors.put("dark_red", ChatColor.DARK_RED); | |
lookupColors.put("green", ChatColor.GREEN); | |
lookupColors.put("dark_green", ChatColor.DARK_GREEN); | |
lookupColors.put("gray", ChatColor.GRAY); | |
lookupColors.put("dark_gray", ChatColor.DARK_GRAY); | |
lookupStyles.put("bold", ChatColor.BOLD); | |
lookupStyles.put("italic", ChatColor.ITALIC); | |
lookupStyles.put("underlined", ChatColor.UNDERLINE); | |
lookupStyles.put("strikethrough", ChatColor.STRIKETHROUGH); | |
lookupStyles.put("obfuscated", ChatColor.MAGIC); | |
} | |
public static String toPlainString(String json) throws ParseException { | |
JSONParser parser = new JSONParser(); | |
Object object = parser.parse(json); | |
StringBuilder builder = new StringBuilder(); | |
Data data = new Data(); | |
data.builder = builder; | |
data.lastColor = ChatColor.WHITE; | |
data.lastStyles = Sets.newHashSet(); | |
apply(object, data); | |
return builder.toString(); | |
} | |
private static void apply(Object element, Data data) { | |
if (!(element instanceof JSONObject) && !(element instanceof JSONArray)) { | |
return; | |
} | |
ChatColor lastColor0 = data.lastColor; | |
Set<ChatColor> lastStyles0 = Sets.newHashSet(data.lastStyles); | |
if (element instanceof JSONObject) { | |
JSONObject object = (JSONObject) element; | |
if (object.containsKey("color")) { | |
ChatColor color = lookupColors.get(object.get("color")); | |
if (color != null) { | |
if (data.lastColor != color) { | |
data.builder.append(color); | |
} | |
} | |
} | |
for (Entry<String, ChatColor> style : lookupStyles.entrySet()) { | |
String key = style.getKey(); | |
ChatColor value = style.getValue(); | |
if (object.containsKey(key)) { | |
if (data.lastStyles.add(value)) { | |
data.builder.append(value); | |
} | |
} else { | |
data.lastStyles.remove(value); | |
} | |
} | |
if (object.containsKey("extra")) { | |
apply(object.get("extra"), data); | |
} | |
} else if (element instanceof JSONArray) { | |
JSONArray array = (JSONArray) element; | |
for (Object object0 : array) { | |
apply(object0, data); | |
} | |
} | |
data.lastColor = lastColor0; | |
data.lastStyles = lastStyles0; | |
} | |
private static class Data { | |
private StringBuilder builder; | |
private ChatColor lastColor; | |
private Set<ChatColor> lastStyles; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title.internal; | |
import io.netty.channel.Channel; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import org.bukkit.Bukkit; | |
import org.bukkit.entity.Player; | |
public class ReflectionHelper { | |
// Methods | |
private static Method m_Player_getHandle; | |
private static Method m_PlayerConnection_sendPacket; | |
// Fields | |
private static Field f_EntityPlayer_playerConnection; | |
private static Field f_PlayerConnection_networkManager; | |
private static Field f_NetworkManager_channel; | |
// The version of the server (nms version like v1_5_R3) | |
private static String nms_version; | |
private static String nms_package; | |
private static String crb_package; | |
public static Object getHandle(Player player) throws Exception { | |
if (m_Player_getHandle == null) { | |
m_Player_getHandle = player.getClass().getMethod("getHandle"); | |
} | |
return m_Player_getHandle.invoke(player); | |
} | |
public static Object getPlayerConnection(Object nms_EntityPlayer) throws Exception { | |
if (f_EntityPlayer_playerConnection == null) { | |
f_EntityPlayer_playerConnection = nms_EntityPlayer.getClass().getField("playerConnection"); | |
} | |
return f_EntityPlayer_playerConnection.get(nms_EntityPlayer); | |
} | |
public static Object getNetworkManager(Object nms_PlayerConnection) throws Exception { | |
if (f_PlayerConnection_networkManager == null) { | |
f_PlayerConnection_networkManager = nms_PlayerConnection.getClass().getField("networkManager"); | |
} | |
return f_PlayerConnection_networkManager.get(nms_PlayerConnection); | |
} | |
public static Channel getChannel(Object nms_NetworkManager) throws Exception { | |
if (f_NetworkManager_channel == null) { | |
f_NetworkManager_channel = ReflectionUtils.findField(nms_NetworkManager.getClass(), Channel.class, 0); | |
} | |
f_NetworkManager_channel.setAccessible(true); | |
return (Channel) f_NetworkManager_channel.get(nms_NetworkManager); | |
} | |
public static Channel getChannel(Player player) throws Exception { | |
return getChannel(getNetworkManager(getPlayerConnection(getHandle(player)))); | |
} | |
public static String getNmsVersion() { | |
if (nms_version == null) { | |
nms_version = Bukkit.getServer().getClass().getPackage().getName().replace("org.bukkit.craftbukkit.", ""); | |
} | |
return nms_version; | |
} | |
public static String getNmsPackage() { | |
if (nms_package == null) { | |
nms_package = "net.minecraft.server." + getNmsVersion(); | |
} | |
return nms_package; | |
} | |
public static String getCrbPackage() { | |
if (crb_package == null) { | |
crb_package = "org.bukkit.craftbukkit." + getNmsVersion(); | |
} | |
return crb_package; | |
} | |
public static Class<?> findNmsClass(String name) throws Exception { | |
return Class.forName(getNmsPackage() + "." + name); | |
} | |
public static Class<?> findCrbClass(String name) throws Exception { | |
return Class.forName(getCrbPackage() + "." + name); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title.internal; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.util.Objects; | |
public class ReflectionUtils { | |
private ReflectionUtils() { | |
} | |
public static Method findMethod(Class<?> target, Class<?> returnType, Class<?>[] args, int index) { | |
for (Method method : target.getDeclaredMethods()) { | |
method.setAccessible(true); | |
if (method.getReturnType() == returnType && Objects.deepEquals(args, method.getParameterTypes())) { | |
if (index == 0) { | |
return method; | |
} | |
index--; | |
} | |
} | |
return null; | |
} | |
public static Field findField(Class<?> target, Class<?> fieldType, int index) { | |
for (Field field : target.getDeclaredFields()) { | |
field.setAccessible(true); | |
if (field.getType().isAssignableFrom(fieldType)) { | |
if (index == 0) { | |
return field; | |
} | |
index--; | |
} | |
} | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title; | |
public class Title { | |
private final String value; | |
private final boolean jsonFormat; | |
public Title(String value, boolean jsonFormat) { | |
this.jsonFormat = jsonFormat; | |
this.value = value; | |
} | |
public Title(String value) { | |
this(value, false); | |
} | |
public String getValue() { | |
return this.value; | |
} | |
public boolean isJsonFormat() { | |
return this.jsonFormat; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package title.internal; | |
import io.netty.channel.Channel; | |
import io.netty.channel.ChannelDuplexHandler; | |
import io.netty.channel.ChannelHandlerContext; | |
import io.netty.channel.ChannelPipeline; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import static title.internal.ReflectionHelper.findCrbClass; | |
import static title.internal.ReflectionHelper.findNmsClass; | |
import static title.internal.ReflectionHelper.getNmsVersion; | |
import static title.internal.ReflectionUtils.findMethod; | |
import static title.internal.ReflectionUtils.findField; | |
import org.bukkit.Bukkit; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.EventPriority; | |
import org.bukkit.event.HandlerList; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.player.PlayerJoinEvent; | |
import org.bukkit.plugin.Plugin; | |
import org.json.simple.parser.ParseException; | |
import title.Title; | |
import title.TitleProvider; | |
public class TitlePacketInjector implements Listener { | |
private final TitleProvider provider; | |
public TitlePacketInjector(TitleProvider provider) { | |
this.provider = provider; | |
} | |
public void onEnable(Plugin plugin) { | |
Bukkit.getPluginManager().registerEvents(this, plugin); | |
for (Player player : Bukkit.getOnlinePlayers()) { | |
this.inject(player, true); | |
} | |
} | |
public void onDisable(Plugin plugin) { | |
for (Player player : Bukkit.getOnlinePlayers()) { | |
this.inject(player, false); | |
} | |
HandlerList.unregisterAll(this); | |
} | |
@EventHandler(priority = EventPriority.HIGHEST) | |
public void onPlayerJoin(PlayerJoinEvent event) { | |
this.inject(event.getPlayer(), true); | |
} | |
private void inject(Player player, boolean inject) { | |
try { | |
this.inject0(player, inject); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
private void inject0(Player player, boolean inject) throws Exception { | |
Channel channel = ReflectionHelper.getChannel(player); | |
ChannelPipeline pipe = channel.pipeline(); | |
if (inject && pipe.get("inventoryTitle_handler") == null) { | |
pipe.addBefore("packet_handler", "inventoryTitle_handler", new Handler(player, this.provider)); | |
} else if (!inject && pipe.get("inventoryTitle_handler") != null) { | |
pipe.remove("inventoryTitle_handler"); | |
} | |
} | |
private static final class Handler extends ChannelDuplexHandler { | |
private static Class<?> t_PacketOpenWindow; | |
private static Method m_CraftChatMessage_fromString; | |
private static Method m_ChatSerializer_fromJsonString; | |
private static Field t_PacketOpenWindow_title; | |
// Whether the title a chat component is | |
private static boolean b_PacketOpenWindow_title_isChatComponent; | |
private final Player player; | |
private final TitleProvider provider; | |
public Handler(Player player, TitleProvider provider) { | |
this.provider = provider; | |
this.player = player; | |
} | |
@Override | |
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | |
if (t_PacketOpenWindow == null) { | |
String version = getNmsVersion(); | |
if (version.startsWith("v1_5_") || version.startsWith("v1_6_")) { | |
t_PacketOpenWindow = findNmsClass("PacketPlayOutOpenWindow"); | |
} else if (version.startsWith("v1_7_") || version.startsWith("v1_8_")) { | |
t_PacketOpenWindow = findNmsClass("Packet100OpenWindow"); | |
} | |
if (version.startsWith("v_1_8_")) { | |
Class<?> t_IChatBaseComponent = findNmsClass("IChatBaseComponent"); | |
t_PacketOpenWindow_title = findField(t_PacketOpenWindow, t_IChatBaseComponent, 0); | |
b_PacketOpenWindow_title_isChatComponent = true; | |
m_CraftChatMessage_fromString = findCrbClass("util.CraftChatMessage").getMethod("fromString", String.class); | |
m_ChatSerializer_fromJsonString = findMethod(findNmsClass("IChatBaseComponent$ChatSerializer"), t_IChatBaseComponent, new Class[] { String.class }, 0); | |
} else { | |
t_PacketOpenWindow_title = findField(t_PacketOpenWindow, String.class, 0); | |
} | |
} | |
if (t_PacketOpenWindow.isInstance(msg)) { | |
Title title = this.provider.provideFor(this.player); | |
String title1 = title == null ? "" : title.getValue(); | |
boolean json = title == null ? false : title.isJsonFormat(); | |
Object title0 = null; | |
if (b_PacketOpenWindow_title_isChatComponent) { | |
if (json) { | |
try { | |
title0 = m_ChatSerializer_fromJsonString.invoke(null, title1); | |
} catch (Exception e) { | |
System.out.println("Invalid json string! (" + title1 + ")"); | |
} | |
} | |
if (title0 == null) { | |
title0 = m_CraftChatMessage_fromString.invoke(null, title1); | |
} | |
} else { | |
if (json) { | |
try { | |
title1 = JsonUtils.toPlainString(title1); | |
} catch (ParseException e) { | |
System.out.println("Invalid json string! (" + title1 + ")"); | |
} | |
} | |
if (title1.length() > 32) { | |
title0 = title1.substring(0, 32); | |
} else { | |
title0 = title1; | |
} | |
} | |
t_PacketOpenWindow_title.setAccessible(true); | |
t_PacketOpenWindow_title.set(msg, title0); | |
} | |
super.channelRead(ctx, msg); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment