Skip to content

Instantly share code, notes, and snippets.

@Cybermaxke
Last active August 29, 2015 14:18
Show Gist options
  • Save Cybermaxke/26be8f87fc2d6f21d3b6 to your computer and use it in GitHub Desktop.
Save Cybermaxke/26be8f87fc2d6f21d3b6 to your computer and use it in GitHub Desktop.
Modify the title of the inventory that is send to the client.
package title;
import org.bukkit.entity.Player;
public interface TitleProvider {
Title provideFor(Player player);
}
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;
}
}
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);
}
}
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;
}
}
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;
}
}
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