From 8bafdd49dc4bc29dba03f06d31e577684fe5f61d Mon Sep 17 00:00:00 2001
From: Josh Roy <10731363+JRoy@users.noreply.github.com>
Date: Sat, 20 Feb 2021 13:12:58 -0500
Subject: [PATCH] Add support for hex color codes in console

---
 ...pport-for-hex-color-codes-in-console.patch | 224 ++++++++++++++++++
 1 file changed, 224 insertions(+)
 create mode 100644 Spigot-Server-Patches/Add-support-for-hex-color-codes-in-console.patch

diff --git a/Spigot-Server-Patches/Add-support-for-hex-color-codes-in-console.patch b/Spigot-Server-Patches/Add-support-for-hex-color-codes-in-console.patch
new file mode 100644
index 0000000000..92ccfa5898
--- /dev/null
+++ b/Spigot-Server-Patches/Add-support-for-hex-color-codes-in-console.patch
@@ -0,0 +1,224 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Josh Roy <10731363+JRoy@users.noreply.github.com>
+Date: Sat, 20 Feb 2021 13:09:59 -0500
+Subject: [PATCH] Add support for hex color codes in console
+
+Converts upstream's hex color code legacy format into actual hex color codes in the console.
+
+diff --git a/src/main/java/io/papermc/paper/console/HexFormattingConverter.java b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.console;
++
++import net.minecrell.terminalconsole.TerminalConsoleAppender;
++import org.apache.logging.log4j.core.LogEvent;
++import org.apache.logging.log4j.core.config.Configuration;
++import org.apache.logging.log4j.core.config.plugins.Plugin;
++import org.apache.logging.log4j.core.layout.PatternLayout;
++import org.apache.logging.log4j.core.pattern.*;
++import org.apache.logging.log4j.util.PerformanceSensitive;
++import org.apache.logging.log4j.util.PropertiesUtil;
++
++import java.awt.*;
++import java.util.List;
++import java.util.regex.Matcher;
++import java.util.regex.Pattern;
++
++import static net.minecrell.terminalconsole.MinecraftFormattingConverter.KEEP_FORMATTING_PROPERTY;
++
++/**
++ * Modified version of <a href="https://github.com/Minecrell/TerminalConsoleAppender/blob/master/src/main/java/net/minecrell/terminalconsole/MinecraftFormattingConverter.java">
++ *     TerminalConsoleAppender's MinecraftFormattingConverter</a> to support hex color codes using the md_5 &x&r&r&g&g&b&b format.
++ */
++@Plugin(name = "paperMinecraftFormatting", category = PatternConverter.CATEGORY)
++@ConverterKeys({ "paperMinecraftFormatting" })
++@PerformanceSensitive("allocation")
++public final class HexFormattingConverter extends LogEventPatternConverter {
++
++    private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY);
++
++    private static final String ANSI_RESET = "\u001B[m";
++
++    private static final char COLOR_CHAR = '§';
++    private static final String LOOKUP = "0123456789abcdefklmnor";
++
++    private static final String RGB_ANSI = "\u001B[38;2;%d;%d;%dm";
++    private static final Pattern RGB_PATTERN = Pattern.compile(COLOR_CHAR + "x(" + COLOR_CHAR + "[0-9a-fA-F]){6}");
++
++    private static final String[] ansiCodes = new String[] {
++        "\u001B[0;30m",    // Black §0
++        "\u001B[0;34m",    // Dark Blue §1
++        "\u001B[0;32m",    // Dark Green §2
++        "\u001B[0;36m",    // Dark Aqua §3
++        "\u001B[0;31m",    // Dark Red §4
++        "\u001B[0;35m",    // Dark Purple §5
++        "\u001B[0;33m",    // Gold §6
++        "\u001B[0;37m",    // Gray §7
++        "\u001B[0;30;1m",  // Dark Gray §8
++        "\u001B[0;34;1m",  // Blue §9
++        "\u001B[0;32;1m",  // Green §a
++        "\u001B[0;36;1m",  // Aqua §b
++        "\u001B[0;31;1m",  // Red §c
++        "\u001B[0;35;1m",  // Light Purple §d
++        "\u001B[0;33;1m",  // Yellow §e
++        "\u001B[0;37;1m",  // White §f
++        "\u001B[5m",       // Obfuscated §k
++        "\u001B[21m",      // Bold §l
++        "\u001B[9m",       // Strikethrough §m
++        "\u001B[4m",       // Underline §n
++        "\u001B[3m",       // Italic §o
++        ANSI_RESET,        // Reset §r
++    };
++
++    private final boolean ansi;
++    private final List<PatternFormatter> formatters;
++
++    /**
++     * Construct the converter.
++     *
++     * @param formatters The pattern formatters to generate the text to manipulate
++     * @param strip      If true, the converter will strip all formatting codes
++     */
++    protected HexFormattingConverter(List<PatternFormatter> formatters, boolean strip) {
++        super("minecraftFormatting", null);
++        this.formatters = formatters;
++        this.ansi = !strip;
++    }
++
++    @Override
++    public void format(LogEvent event, StringBuilder toAppendTo) {
++        int start = toAppendTo.length();
++        //noinspection ForLoopReplaceableByForEach
++        for (int i = 0, size = formatters.size(); i < size; i++) {
++            formatters.get(i).format(event, toAppendTo);
++        }
++
++        if (KEEP_FORMATTING || toAppendTo.length() == start) {
++            // Skip replacement if disabled or if the content is empty
++            return;
++        }
++
++        boolean useAnsi = ansi && TerminalConsoleAppender.isAnsiSupported();
++        String content = useAnsi ? convertRGBColors(toAppendTo.substring(start)) : stripRGBColors(toAppendTo.substring(start));
++        format(content, toAppendTo, start, useAnsi);
++    }
++
++    private static String convertRGBColors(String input) {
++        Matcher matcher = RGB_PATTERN.matcher(input);
++        StringBuffer buffer = new StringBuffer();
++        while (matcher.find()) {
++            String s = matcher.group().replace(String.valueOf(COLOR_CHAR), "").replace('x', '#');
++            Color color = Color.decode(s);
++            int red = color.getRed();
++            int blue = color.getBlue();
++            int green = color.getGreen();
++            String replacement = String.format(RGB_ANSI, red, green, blue);
++            matcher.appendReplacement(buffer, replacement);
++        }
++        matcher.appendTail(buffer);
++        return buffer.toString();
++    }
++
++    private static String stripRGBColors(String input) {
++        Matcher matcher = RGB_PATTERN.matcher(input);
++        StringBuffer buffer = new StringBuffer();
++        while (matcher.find()) {
++            matcher.appendReplacement(buffer, "");
++        }
++        matcher.appendTail(buffer);
++        return buffer.toString();
++    }
++
++    static void format(String s, StringBuilder result, int start, boolean ansi) {
++        int next = s.indexOf(COLOR_CHAR);
++        int last = s.length() - 1;
++        if (next == -1 || next == last) {
++            return;
++        }
++
++        result.setLength(start + next);
++
++        int pos = next;
++        do {
++            int format = LOOKUP.indexOf(Character.toLowerCase(s.charAt(next + 1)));
++            if (format != -1) {
++                if (pos != next) {
++                    result.append(s, pos, next);
++                }
++                if (ansi) {
++                    result.append(ansiCodes[format]);
++                }
++                pos = next += 2;
++            } else {
++                next++;
++            }
++
++            next = s.indexOf(COLOR_CHAR, next);
++        } while (next != -1 && next < last);
++
++        result.append(s, pos, s.length());
++        if (ansi) {
++            result.append(ANSI_RESET);
++        }
++    }
++
++    /**
++     * Gets a new instance of the {@link HexFormattingConverter} with the
++     * specified options.
++     *
++     * @param config  The current configuration
++     * @param options The pattern options
++     * @return The new instance
++     *
++     * @see HexFormattingConverter
++     */
++    public static HexFormattingConverter newInstance(Configuration config, String[] options) {
++        if (options.length < 1 || options.length > 2) {
++            LOGGER.error("Incorrect number of options on paperMinecraftFormatting. Expected at least 1, max 2 received " + options.length);
++            return null;
++        }
++        if (options[0] == null) {
++            LOGGER.error("No pattern supplied on paperMinecraftFormatting");
++            return null;
++        }
++
++        PatternParser parser = PatternLayout.createPatternParser(config);
++        List<PatternFormatter> formatters = parser.parse(options[0]);
++        boolean strip = options.length > 1 && "strip".equals(options[1]);
++        return new HexFormattingConverter(formatters, strip);
++    }
++
++}
+diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/resources/log4j2.xml
++++ b/src/main/resources/log4j2.xml
+@@ -0,0 +0,0 @@
+         </Queue>
+         <TerminalConsole name="TerminalConsole">
+             <PatternLayout>
+-                <LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss} %level]: [%logger] %minecraftFormatting{%msg}%n%xEx{full}}">
++                <LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss} %level]: [%logger] %paperMinecraftFormatting{%msg}%n%xEx{full}}">
+                     <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+                     <!-- Disable prefix for various plugins that bypass the plugin logger -->
+                     <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+-                                  pattern="%highlightError{[%d{HH:mm:ss} %level]: %minecraftFormatting{%msg}%n%xEx{full}}" />
++                                  pattern="%highlightError{[%d{HH:mm:ss} %level]: %paperMinecraftFormatting{%msg}%n%xEx{full}}" />
+                 </LoggerNamePatternSelector>
+             </PatternLayout>
+         </TerminalConsole>
+         <RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
+             <PatternLayout>
+-                <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level]: [%logger] %minecraftFormatting{%msg}{strip}%n%xEx{full}">
++                <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level]: [%logger] %paperMinecraftFormatting{%msg}{strip}%n%xEx{full}">
+                     <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+                     <!-- Disable prefix for various plugins that bypass the plugin logger -->
+                     <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+-                                  pattern="[%d{HH:mm:ss}] [%t/%level]: %minecraftFormatting{%msg}{strip}%n%xEx{full}" />
++                                  pattern="[%d{HH:mm:ss}] [%t/%level]: %paperMinecraftFormatting{%msg}{strip}%n%xEx{full}" />
+                 </LoggerNamePatternSelector>
+             </PatternLayout>
+             <Policies>