Skip to content

Commit d3a04e2

Browse files
tastybentoclaude
andcommitted
Fix BungeeCord hex colour format not recognised in legacyToMiniMessage
translateColorCodes serialises &#RRGGBB to §x§R§R§G§G§B§B (BungeeCord format) via LegacyComponentSerializer. When that string was subsequently passed to parseMiniMessageOrLegacy → legacyToMiniMessage, the &x prefix was silently ignored and each following &R digit was misread as a named colour code (&2 = dark_green, &3 = dark_aqua, …), producing completely wrong colours. Fix: after normalising § to &, detect the &x&R&R&G&G&B&B pattern in both legacyToMiniMessage and replaceLegacyCodesInline and convert it to the &#RRGGBB form that HEX_PATTERN already handles. Fixes #2943 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6bdd0cd commit d3a04e2

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

src/main/java/world/bentobox/bentobox/util/Util.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ public class Util {
9898
*/
9999
private static final Pattern LEGACY_HEX_CODE_PATTERN = Pattern.compile("&#[0-9a-fA-F]{3,6}|\u00A7x(\u00A7[0-9a-fA-F]){6}");
100100

101+
/**
102+
* Pattern to match the BungeeCord/Spigot {@code &x&R&R&G&G&B&B} hex format
103+
* (after {@code §} has been normalised to {@code &}).
104+
* Produced by {@link LegacyComponentSerializer} with {@code useUnusualXRepeatedCharacterHexFormat()}.
105+
*/
106+
private static final Pattern BUNGEE_HEX_PATTERN = Pattern.compile("&x(&[0-9a-fA-F]){6}");
107+
101108
/**
102109
* MiniMessage instance for parsing MiniMessage-formatted strings.
103110
*/
@@ -1047,6 +1054,23 @@ public static String legacyToMiniMessage(@NonNull String legacy) {
10471054
// First, normalize § to & for uniform processing
10481055
String text = legacy.replace('\u00A7', '&');
10491056

1057+
// Convert BungeeCord/Spigot §x§R§R§G§G§B§B hex format (now &x&R&R...) to &#RRGGBB
1058+
// so the HEX_PATTERN step below handles all hex input uniformly.
1059+
// This format is emitted by LegacyComponentSerializer.useUnusualXRepeatedCharacterHexFormat()
1060+
// and must be re-parsed here because legacyToMiniMessage does not recognise &x sequences.
1061+
Matcher bungeeMatcher = BUNGEE_HEX_PATTERN.matcher(text);
1062+
if (bungeeMatcher.find()) {
1063+
StringBuilder bungeeBuffer = new StringBuilder();
1064+
bungeeMatcher.reset();
1065+
while (bungeeMatcher.find()) {
1066+
// "&x&2&3&8&a&f&0" → strip "&x" prefix and remaining "&" chars → "238af0"
1067+
String digits = bungeeMatcher.group(0).substring(2).replace("&", "");
1068+
bungeeMatcher.appendReplacement(bungeeBuffer, "&#" + digits);
1069+
}
1070+
bungeeMatcher.appendTail(bungeeBuffer);
1071+
text = bungeeBuffer.toString();
1072+
}
1073+
10501074
// Convert hex codes &#RRGGBB → <color:#RRGGBB>
10511075
Matcher hexMatcher = HEX_PATTERN.matcher(text);
10521076
StringBuilder sb = new StringBuilder();
@@ -1211,6 +1235,18 @@ public static String legacyToMiniMessage(@NonNull String legacy) {
12111235
public static String replaceLegacyCodesInline(@NonNull String text) {
12121236
// Normalize § to &
12131237
text = text.replace('\u00A7', '&');
1238+
// Convert BungeeCord/Spigot &x&R&R&G&G&B&B hex format to &#RRGGBB (see legacyToMiniMessage)
1239+
Matcher bungeeMatcher = BUNGEE_HEX_PATTERN.matcher(text);
1240+
if (bungeeMatcher.find()) {
1241+
StringBuilder bungeeBuffer = new StringBuilder();
1242+
bungeeMatcher.reset();
1243+
while (bungeeMatcher.find()) {
1244+
String digits = bungeeMatcher.group(0).substring(2).replace("&", "");
1245+
bungeeMatcher.appendReplacement(bungeeBuffer, "&#" + digits);
1246+
}
1247+
bungeeMatcher.appendTail(bungeeBuffer);
1248+
text = bungeeBuffer.toString();
1249+
}
12141250
// Replace hex codes &#RRGGBB → <color:#RRGGBB>
12151251
Matcher hexMatcher = HEX_PATTERN.matcher(text);
12161252
StringBuilder sb = new StringBuilder();

src/test/java/world/bentobox/bentobox/util/LegacyToMiniMessageTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
56
import static org.junit.jupiter.api.Assertions.assertTrue;
67

78
import net.kyori.adventure.text.Component;
@@ -285,4 +286,45 @@ void testStripSpaceAfterColorCodesRespectsBoundary() {
285286
// Reset must NOT strip
286287
assertEquals("\u00A7r world", Util.stripSpaceAfterColorCodes("\u00A7r world"));
287288
}
289+
290+
/**
291+
* Regression for <a href="https://github.com/BentoBoxWorld/BentoBox/issues/2943">BentoBox#2943</a>:
292+
* hex colors using the {@code &#RRGGBB} format were broken because
293+
* {@code translateColorCodes} serialises them to the BungeeCord
294+
* {@code §x§R§R§G§G§B§B} format, which {@code legacyToMiniMessage} (and
295+
* {@code replaceLegacyCodesInline}) did not recognise. The colour was then
296+
* corrupted to a sequence of named colours (&amp;2, &amp;3, …) instead of
297+
* the intended hex value.
298+
*/
299+
@Test
300+
void testBungeeCordHexFormatRoundTrip() {
301+
// Simulate the full path: user writes &#238af0&l in a locale file.
302+
// convertToLegacy (pure-legacy path) calls translateColorCodes which
303+
// serialises to §x§2§3§8§a§f§0§l. sendRawMessage then calls
304+
// parseMiniMessageOrLegacy which must reconstruct the original colour.
305+
String bungeeHex = "\u00A7x\u00A72\u00A73\u00A78\u00A7a\u00A7f\u00A70\u00A7l";
306+
Component component = Util.parseMiniMessageOrLegacy(bungeeHex + "test");
307+
// The component must carry the original hex colour, not a named-colour approximation.
308+
net.kyori.adventure.text.format.TextColor color = component.children().isEmpty()
309+
? component.color()
310+
: component.children().get(0).color();
311+
assertNotNull(color, "Expected a colour on the component");
312+
assertEquals(0x238af0, color.value(), "Hex colour #238af0 must survive the BungeeCord round-trip");
313+
}
314+
315+
/**
316+
* {@code &#RRGGBB&l} (the raw user-written format) must also round-trip correctly
317+
* through {@code legacyToMiniMessage}.
318+
*/
319+
@Test
320+
void testRawHexWithBoldRoundTrip() {
321+
String mm = Util.legacyToMiniMessage("&#238af0&lBold text");
322+
Component component = Util.parseMiniMessage(mm);
323+
// Must have the correct hex colour
324+
net.kyori.adventure.text.format.TextColor color = component.color() != null
325+
? component.color()
326+
: component.children().isEmpty() ? null : component.children().get(0).color();
327+
assertNotNull(color, "Expected a colour");
328+
assertEquals(0x238af0, color.value());
329+
}
288330
}

0 commit comments

Comments
 (0)