More ReflectionManager stuff
This commit is contained in:
		| @@ -7,15 +7,14 @@ import java.io.InputStreamReader; | |||||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
| import java.lang.reflect.Modifier; | import java.lang.reflect.Modifier; | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| import java.util.regex.Matcher; | import java.util.regex.Matcher; | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
| import com.google.common.collect.ImmutableMap; | import com.google.common.collect.ImmutableMap; | ||||||
|  | import me.libraryaddict.disguise.disguisetypes.DisguiseType; | ||||||
| import org.bukkit.Art; | import org.bukkit.Art; | ||||||
| import org.bukkit.Bukkit; | import org.bukkit.Bukkit; | ||||||
| import org.bukkit.Location; | import org.bukkit.Location; | ||||||
| @@ -76,36 +75,31 @@ public class ReflectionManager { | |||||||
|      */ |      */ | ||||||
|     private static Map<String, Map<String, String>> ForgeFieldMappings; |     private static Map<String, Map<String, String>> ForgeFieldMappings; | ||||||
|     /** |     /** | ||||||
|      * Map of Forge fully qualified class names to a map from mc-dev method names to Forge method names. |      * Map of Forge fully qualified class names to a map from mc-dev method names to a map from method signatures to Forge method names. | ||||||
|      * |  | ||||||
|      * There may be a mapping from null in the innermost map, which may be ignored. |  | ||||||
|      */ |      */ | ||||||
|     private static Map<String, Map<String, Map<Class<?>[], String>>> ForgeMethodMappings; |     private static Map<String, Map<String, Map<String, String>>> ForgeMethodMappings; | ||||||
|     private static Map<String, Class<?>> primitiveTypes; |     private static Map<Class<?>, String> primitiveTypes; | ||||||
|     private static Pattern signatureSegment; |  | ||||||
|  |  | ||||||
|     static { |     static { | ||||||
|         final String nameseg_class = "a-zA-Z0-9$_"; |         final String nameseg_class = "a-zA-Z0-9$_"; | ||||||
|         final String fqn_class = "a-zA-Z0-9$_/"; |         final String fqn_class = nameseg_class + "/"; | ||||||
|         final String fqn_component = "L[" + fqn_class + "]+;"; |  | ||||||
|  |  | ||||||
|         signatureSegment = Pattern.compile("\\[*(?:Z|B|C|S|I|J|F|D|V|" + fqn_component + ")"); |         primitiveTypes = ImmutableMap.<Class<?>, String>builder() | ||||||
|         primitiveTypes = ImmutableMap.<String, Class<?>>builder() |                 .put(boolean.class,"Z") | ||||||
|                 .put("Z", boolean.class) |                 .put(byte.class,   "B") | ||||||
|                 .put("B", byte.class) |                 .put(char.class,   "C") | ||||||
|                 .put("C", char.class) |                 .put(short.class,  "S") | ||||||
|                 .put("S", short.class) |                 .put(int.class,    "I") | ||||||
|                 .put("I", int.class) |                 .put(long.class,   "J") | ||||||
|                 .put("J", long.class) |                 .put(float.class,  "F") | ||||||
|                 .put("F", float.class) |                 .put(double.class, "D") | ||||||
|                 .put("D", double.class) |                 .put(void.class,   "V").build(); | ||||||
|                 .put("V", void.class).build(); |  | ||||||
|  |  | ||||||
|         if (isForge) { |         if (isForge) { | ||||||
|             // Initialize the maps by reading the srg file |             // Initialize the maps by reading the srg file | ||||||
|             ForgeClassMappings = new HashMap<String, String>(); |             ForgeClassMappings = new HashMap<String, String>(); | ||||||
|             ForgeFieldMappings = new HashMap<String, Map<String, String>>(); |             ForgeFieldMappings = new HashMap<String, Map<String, String>>(); | ||||||
|             ForgeMethodMappings = new HashMap<String, Map<String, Map<Class<?>[], String>>>(); |             ForgeMethodMappings = new HashMap<String, Map<String, Map<String, String>>>(); | ||||||
|             try { |             try { | ||||||
|                 InputStream stream = Class.forName("net.minecraftforge.common.MinecraftForge").getClassLoader() |                 InputStream stream = Class.forName("net.minecraftforge.common.MinecraftForge").getClassLoader() | ||||||
|                         .getResourceAsStream("mappings/" + getBukkitVersion() + "/cb2numpkg.srg"); |                         .getResourceAsStream("mappings/" + getBukkitVersion() + "/cb2numpkg.srg"); | ||||||
| @@ -153,49 +147,37 @@ public class ReflectionManager { | |||||||
|                     Matcher methodMatcher = methodPattern.matcher(line); |                     Matcher methodMatcher = methodPattern.matcher(line); | ||||||
|                     if (methodMatcher.matches()) { |                     if (methodMatcher.matches()) { | ||||||
|                         // get by CB class name |                         // get by CB class name | ||||||
|                         Map<String, Map<Class<?>[], String>> middleMap = ForgeMethodMappings.get(dir2fqn(methodMatcher.group(5))); |                         Map<String, Map<String, String>> middleMap = ForgeMethodMappings.get(dir2fqn(methodMatcher.group(5))); | ||||||
|                         if (middleMap == null) { |                         if (middleMap == null) { | ||||||
|                             middleMap = new HashMap<String, Map<Class<?>[], String>>(); |                             middleMap = new HashMap<String, Map<String, String>>(); | ||||||
|                             ForgeMethodMappings.put(dir2fqn(methodMatcher.group(5)), middleMap); |                             ForgeMethodMappings.put(dir2fqn(methodMatcher.group(5)), middleMap); | ||||||
|                         } |                         } | ||||||
|                         // get by CB method name |                         // get by CB method name | ||||||
|                         Map<Class<?>[], String> innerMap = middleMap.get(methodMatcher.group(2)); |                         Map<String, String> innerMap = middleMap.get(methodMatcher.group(2)); | ||||||
|                         if (innerMap == null) { |                         if (innerMap == null) { | ||||||
|                             innerMap = new HashMap<Class<?>[], String>(); |                             innerMap = new HashMap<String, String>(); | ||||||
|                             middleMap.put(methodMatcher.group(2), innerMap); |                             middleMap.put(methodMatcher.group(2), innerMap); | ||||||
|                         } |                         } | ||||||
|                         // parse the class array |                         // store the parameter strings | ||||||
|                         Class<?>[] argsCb = null, argsForge = null; |                         innerMap.put(methodMatcher.group(3), methodMatcher.group(6)); | ||||||
|                         try { |                         innerMap.put(methodMatcher.group(7), methodMatcher.group(6)); | ||||||
|                             argsCb = parseSignatureArguments(methodMatcher.group(3)); |  | ||||||
|                         } catch (Throwable ignored) { |  | ||||||
|                         } |  | ||||||
|                         try { |  | ||||||
|                             argsForge = parseSignatureArguments(methodMatcher.group(7)); |  | ||||||
|                         } catch (Throwable ignored) { |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         innerMap.put(argsCb, methodMatcher.group(6)); |  | ||||||
|                         innerMap.put(argsForge, methodMatcher.group(6)); |  | ||||||
|                         System.out.println(methodMatcher.group(5) + "/" + methodMatcher.group(2) + "(" + argsForge + ") -> " + methodMatcher.group(6)); |  | ||||||
|                         continue; |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 System.out.println("[LibsDisguises] Loaded in Cauldron/Forge mode"); |                 System.out.println("[LibsDisguises] Loaded in Cauldron/Forge mode"); | ||||||
|                 System.out.println("[LibsDisguises]" + ForgeClassMappings.size() + " Cauldron class mappings loaded"); |                 System.out.println("[LibsDisguises] Loaded " + ForgeClassMappings.size() + " Cauldron class mappings"); | ||||||
|                 System.out.println("[LibsDisguises]" + ForgeFieldMappings.size() + " Cauldron field mappings loaded"); |                 System.out.println("[LibsDisguises] Loaded " + ForgeFieldMappings.size() + " Cauldron field mappings"); | ||||||
|                 System.out.println("[LibsDisguises]" + ForgeMethodMappings.size() + " Cauldron method mappings loaded"); |                 System.out.println("[LibsDisguises] Loaded " + ForgeMethodMappings.size() + " Cauldron method mappings"); | ||||||
|             } catch (ClassNotFoundException e) { |             } catch (ClassNotFoundException e) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 System.err.println("Warning: Running on Cauldron server, but couldn't load mappings file"); |                 System.err.println("Warning: Running on Cauldron server, but couldn't load mappings file. LibsDisguises will likely crash!"); | ||||||
|             } catch (IOException e) { |             } catch (IOException e) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 System.err.println("Warning: Running on Cauldron server, but couldn't load mappings file"); |                 System.err.println("Warning: Running on Cauldron server, but couldn't load mappings file. LibsDisguises will likely crash!"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static final Class craftItemClass; |     private static final Class<?> craftItemClass; | ||||||
|     private static final Field pingField; |     private static final Field pingField; | ||||||
|     private static final Field trackerField; |     private static final Field trackerField; | ||||||
|     private static final Field entitiesField; |     private static final Field entitiesField; | ||||||
| @@ -224,36 +206,17 @@ public class ReflectionManager { | |||||||
|         trackerField = getNmsField("WorldServer", "tracker"); |         trackerField = getNmsField("WorldServer", "tracker"); | ||||||
|         entitiesField = getNmsField("EntityTracker", "trackedEntities"); |         entitiesField = getNmsField("EntityTracker", "trackedEntities"); | ||||||
|         ihmGet = getNmsMethod("IntHashMap", "get", int.class); |         ihmGet = getNmsMethod("IntHashMap", "get", int.class); | ||||||
|  |  | ||||||
|  |         Method m = getNmsMethod("Item", "getItemOf", getNmsClass("Block")); | ||||||
|  |         System.out.println(m); | ||||||
|  |  | ||||||
|  |         DisguiseType.ARROW.isMisc(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static String dir2fqn(String s) { |     private static String dir2fqn(String s) { | ||||||
|         return s.replaceAll("/", "."); |         return s.replaceAll("/", "."); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Class<?>[] parseSignatureArguments(String args) throws ClassNotFoundException { |  | ||||||
|         List<Class<?>> classes = new ArrayList<Class<?>>(); |  | ||||||
|         Matcher matcher = signatureSegment.matcher(args); |  | ||||||
|         while (matcher.find()) { |  | ||||||
|             classes.add(parseClass(matcher.group())); |  | ||||||
|         } |  | ||||||
|         return classes.toArray(new Class<?>[classes.size()]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static Class<?> parseClass(String str) throws ClassNotFoundException { |  | ||||||
|         if (str.startsWith("[")) { |  | ||||||
|             // Array |  | ||||||
|             // http://stackoverflow.com/a/4901192/1210278 |  | ||||||
|             return java.lang.reflect.Array.newInstance(parseClass(str.substring(1)), 0).getClass(); |  | ||||||
|         } else if (str.length() == 1) { |  | ||||||
|             return primitiveTypes.get(str); |  | ||||||
|         } else if (str.startsWith("L")) { |  | ||||||
|             // Chop off L and ; |  | ||||||
|             return Class.forName(str.substring(1, str.length() - 1)); |  | ||||||
|         } else { |  | ||||||
|             throw new ClassNotFoundException("Malformed method signature fragment? Argument: " + str); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // === |     // === | ||||||
|  |  | ||||||
|     public static Object createEntityInstance(String entityName) { |     public static Object createEntityInstance(String entityName) { | ||||||
| @@ -327,7 +290,7 @@ public class ReflectionManager { | |||||||
|  |  | ||||||
|     public static Entity getBukkitEntity(Object nmsEntity) { |     public static Entity getBukkitEntity(Object nmsEntity) { | ||||||
|         try { |         try { | ||||||
|             return (Entity) ReflectionManager.getNmsClass("Entity").getMethod("getBukkitEntity").invoke(nmsEntity); |             return (Entity) getNmsMethod("Entity", "getBukkitEntity").invoke(nmsEntity); | ||||||
|         } catch (Exception ex) { |         } catch (Exception ex) { | ||||||
|             ex.printStackTrace(); |             ex.printStackTrace(); | ||||||
|         } |         } | ||||||
| @@ -347,7 +310,7 @@ public class ReflectionManager { | |||||||
|         return bukkitVersion; |         return bukkitVersion; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Class getCraftClass(String className) { |     public static Class<?> getCraftClass(String className) { | ||||||
|         try { |         try { | ||||||
|             return Class.forName("org.bukkit.craftbukkit." + getBukkitVersion() + "." + className); |             return Class.forName("org.bukkit.craftbukkit." + getBukkitVersion() + "." + className); | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
| @@ -358,8 +321,7 @@ public class ReflectionManager { | |||||||
|  |  | ||||||
|     public static String getCraftSound(Sound sound) { |     public static String getCraftSound(Sound sound) { | ||||||
|         try { |         try { | ||||||
|             Class c = getCraftClass("CraftSound"); |             return (String) getCraftClass("CraftSound").getMethod("getSound", Sound.class).invoke(null, sound); | ||||||
|             return (String) c.getMethod("getSound", Sound.class).invoke(null, sound); |  | ||||||
|         } catch (Exception ex) { |         } catch (Exception ex) { | ||||||
|             ex.printStackTrace(); |             ex.printStackTrace(); | ||||||
|         } |         } | ||||||
| @@ -368,8 +330,7 @@ public class ReflectionManager { | |||||||
|  |  | ||||||
|     public static String getEnumArt(Art art) { |     public static String getEnumArt(Art art) { | ||||||
|         try { |         try { | ||||||
|             Class craftArt = Class.forName("org.bukkit.craftbukkit." + getBukkitVersion() + ".CraftArt"); |             Object enumArt = getCraftClass("CraftArt").getMethod("BukkitToNotch", Art.class).invoke(null, art); | ||||||
|             Object enumArt = craftArt.getMethod("BukkitToNotch", Art.class).invoke(null, art); |  | ||||||
|             for (Field field : enumArt.getClass().getFields()) { |             for (Field field : enumArt.getClass().getFields()) { | ||||||
|                 if (field.getType() == String.class) { |                 if (field.getType() == String.class) { | ||||||
|                     return (String) field.get(enumArt); |                     return (String) field.get(enumArt); | ||||||
| @@ -416,15 +377,17 @@ public class ReflectionManager { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Class getNmsClass(String className) { |     public static Class getNmsClass(String className) { | ||||||
|         try { |         if (isForge) { | ||||||
|             if (isForge) { |             String forgeName = ForgeClassMappings.get(className); | ||||||
|                 String forgeName = ForgeClassMappings.get(className); |             if (forgeName != null) { | ||||||
|                 if (forgeName == null) { |                 try { | ||||||
|                     throw new RuntimeException("Missing Forge mapping for " + className); |                     return Class.forName(forgeName); | ||||||
|  |                 } catch (ClassNotFoundException ignored) { | ||||||
|                 } |                 } | ||||||
|                 return Class.forName(forgeName); |             } else | ||||||
|             } |                 throw new RuntimeException("Missing Forge mapping for " + className); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|             return Class.forName("net.minecraft.server." + getBukkitVersion() + "." + className); |             return Class.forName("net.minecraft.server." + getBukkitVersion() + "." + className); | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             e.printStackTrace(); |             e.printStackTrace(); | ||||||
| @@ -455,14 +418,21 @@ public class ReflectionManager { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Field getNmsField(Class clazz, String fieldName) { |     public static Field getNmsField(Class clazz, String fieldName) { | ||||||
|         try { |         if (isForge) { | ||||||
|             if (isForge) { |             Map<String, String> fieldMap = ForgeFieldMappings.get(clazz.getName()); | ||||||
|                 Map<String, String> fieldMap = ForgeFieldMappings.get(clazz.getName()); |             if (fieldMap != null) { | ||||||
|                 if (fieldMap == null) { throw new RuntimeException("No field mappings for " + clazz.getName()); } |  | ||||||
|                 String forgeName = fieldMap.get(fieldName); |                 String forgeName = fieldMap.get(fieldName); | ||||||
|                 if (forgeName == null) { throw new RuntimeException("No field mapping for " + clazz.getName() + "." + fieldName); } |                 if (forgeName != null) { | ||||||
|                 return clazz.getField(forgeName); |                     try { | ||||||
|             } |                         return clazz.getField(forgeName); | ||||||
|  |                     } catch (NoSuchFieldException ignored) { | ||||||
|  |                     } | ||||||
|  |                 } else | ||||||
|  |                     throw new RuntimeException("No field mapping for " + clazz.getName() + "." + fieldName); | ||||||
|  |             } else | ||||||
|  |                 throw new RuntimeException("No field mappings for " + clazz.getName()); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|             return clazz.getField(fieldName); |             return clazz.getField(fieldName); | ||||||
|         } catch (NoSuchFieldException e) { |         } catch (NoSuchFieldException e) { | ||||||
|             e.printStackTrace(); |             e.printStackTrace(); | ||||||
| @@ -474,17 +444,40 @@ public class ReflectionManager { | |||||||
|         return getNmsMethod(getNmsClass(className), methodName, parameters); |         return getNmsMethod(getNmsClass(className), methodName, parameters); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static String methodSignaturePart(Class<?> param) { | ||||||
|  |         if (param.isArray()) { | ||||||
|  |             return "[" + methodSignaturePart(param.getComponentType()); | ||||||
|  |         } else if (param.isPrimitive()) { | ||||||
|  |             return primitiveTypes.get(param); | ||||||
|  |         } else { | ||||||
|  |             return "L" + param.getName().replaceAll("\\.", "/") + ";"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static Method getNmsMethod(Class<?> clazz, String methodName, Class<?>... parameters) { |     public static Method getNmsMethod(Class<?> clazz, String methodName, Class<?>... parameters) { | ||||||
|  |         if (isForge) { | ||||||
|  |             Map<String, Map<String, String>> middleMap = ForgeMethodMappings.get(clazz.getName()); | ||||||
|  |             if (middleMap != null) { | ||||||
|  |                 Map<String, String> innerMap = middleMap.get(methodName); | ||||||
|  |                 if (innerMap != null) { | ||||||
|  |                     StringBuilder sb = new StringBuilder(); | ||||||
|  |                     for (Class<?> cl : parameters) { | ||||||
|  |                         sb.append(methodSignaturePart(cl)); | ||||||
|  |                     } | ||||||
|  |                     String trName = innerMap.get(sb.toString()); | ||||||
|  |                     if (trName != null) { | ||||||
|  |                         try { | ||||||
|  |                             return clazz.getMethod(trName, parameters); | ||||||
|  |                         } catch (NoSuchMethodException ignored) { | ||||||
|  |                         } | ||||||
|  |                     } else | ||||||
|  |                         throw new RuntimeException("No method mapping for " + clazz.getName() + "." + methodName + "(" + sb.toString() + ")"); | ||||||
|  |                 } else | ||||||
|  |                     throw new RuntimeException("No method mapping for " + clazz.getName() + "." + methodName); | ||||||
|  |             } else | ||||||
|  |                 throw new RuntimeException("No method mappings for " + clazz.getName()); | ||||||
|  |         } | ||||||
|         try { |         try { | ||||||
|             if (isForge) { |  | ||||||
|                 Map<String, Map<Class<?>[], String>> middleMap = ForgeMethodMappings.get(clazz.getName()); |  | ||||||
|                 if (middleMap == null) { throw new RuntimeException("No method mappings for " + clazz.getName()); } |  | ||||||
|                 Map<Class<?>[], String> innerMap = middleMap.get(methodName); |  | ||||||
|                 if (innerMap == null) { throw new RuntimeException("No method mapping for " + clazz.getName() + "." + methodName); } |  | ||||||
|                 String trName = innerMap.get(parameters); |  | ||||||
|                 if (trName == null) { throw new RuntimeException("No method mapping for " + clazz.getName() + "." + methodName + "(" + parameters + ")"); } |  | ||||||
|                 return clazz.getMethod(trName, parameters); |  | ||||||
|             } |  | ||||||
|             return clazz.getMethod(methodName, parameters); |             return clazz.getMethod(methodName, parameters); | ||||||
|         } catch (NoSuchMethodException e) { |         } catch (NoSuchMethodException e) { | ||||||
|             e.printStackTrace(); |             e.printStackTrace(); | ||||||
| @@ -550,7 +543,7 @@ public class ReflectionManager { | |||||||
|  |  | ||||||
|     public static WrappedGameProfile grabProfileAddUUID(String playername) { |     public static WrappedGameProfile grabProfileAddUUID(String playername) { | ||||||
|         try { |         try { | ||||||
|             Object minecraftServer = getNmsClass("MinecraftServer").getMethod("getServer").invoke(null); |             Object minecraftServer = getNmsMethod("MinecraftServer", "getServer").invoke(null); | ||||||
|             for (Method method : getNmsClass("MinecraftServer").getMethods()) { |             for (Method method : getNmsClass("MinecraftServer").getMethods()) { | ||||||
|                 if (method.getReturnType().getSimpleName().equals("GameProfileRepository")) { |                 if (method.getReturnType().getSimpleName().equals("GameProfileRepository")) { | ||||||
|                     Object profileRepo = method.invoke(minecraftServer); |                     Object profileRepo = method.invoke(minecraftServer); | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| package me.libraryaddict.disguise.utilities; |  | ||||||
|  |  | ||||||
| import org.junit.Test; |  | ||||||
|  |  | ||||||
| import static org.junit.Assert.assertEquals; |  | ||||||
|  |  | ||||||
| public class ReflectionManagerTests { |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testParseSignatureArguments() throws Exception { |  | ||||||
|         Class<?>[] expect, actual; |  | ||||||
|  |  | ||||||
|         expect = new Class<?>[] {boolean.class, byte.class, char.class, short.class, int.class, long.class, float.class, double.class}; |  | ||||||
|         actual = ReflectionManager.parseSignatureArguments("ZBCSIJFD"); |  | ||||||
|         assertEquals(expect, actual); |  | ||||||
|  |  | ||||||
|         expect = new Class<?>[] {int.class, String[].class, int.class, String.class}; |  | ||||||
|         actual = ReflectionManager.parseSignatureArguments("I[Ljava/lang/String;ILjava/lang/String;"); |  | ||||||
|         assertEquals(expect, actual); |  | ||||||
|  |  | ||||||
|         expect = new Class<?>[] {}; |  | ||||||
|         actual = ReflectionManager.parseSignatureArguments(""); |  | ||||||
|         assertEquals(expect, actual); |  | ||||||
|  |  | ||||||
|         expect = new Class<?>[] {boolean[][][][][][].class}; |  | ||||||
|         actual = ReflectionManager.parseSignatureArguments("[[[[[[Z"); |  | ||||||
|         assertEquals(expect, actual); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user