From 5299ce93b5e79f77d8ff1623a7bf47cbb35f99e3 Mon Sep 17 00:00:00 2001
From: ShulkerSakura <2531493755@qq.com>
Date: Wed, 3 Sep 2025 11:31:53 +0800
Subject: [PATCH 1/2] Added Format Code Supported
---
.../java/low/citory/util/MinecraftANSI.java | 137 ++++++++++++++++++
.../java/low/citory/util/MinecraftColors.java | 59 +++++---
.../java/low/citory/util/MinecraftFormat.java | 30 ++++
.../java/low/citory/util/TextColorizer.java | 41 +++---
4 files changed, 229 insertions(+), 38 deletions(-)
create mode 100644 src/main/java/low/citory/util/MinecraftANSI.java
create mode 100644 src/main/java/low/citory/util/MinecraftFormat.java
diff --git a/src/main/java/low/citory/util/MinecraftANSI.java b/src/main/java/low/citory/util/MinecraftANSI.java
new file mode 100644
index 0000000..84c7a7e
--- /dev/null
+++ b/src/main/java/low/citory/util/MinecraftANSI.java
@@ -0,0 +1,137 @@
+package low.citory.util;
+
+public class MinecraftANSI {
+
+ /**
+ * 将 Minecraft 的 § 颜色代码转换为 ANSI 转义序列
+ * 用于在支持 ANSI 的终端中显示颜色
+ */
+ public static String toAnsi(String text) {
+ if (text == null) return null;
+
+ StringBuilder result = new StringBuilder();
+ boolean inColor = false;
+ boolean inFormat = false;
+
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (c == '§' && i + 1 < text.length()) {
+ char code = text.charAt(i + 1);
+ MinecraftColors color = MinecraftColors.fromChar(code);
+ if (color != null) {
+ result.append(color.getAnsiCode());
+ inColor = true;
+ i++; // 跳过 color code
+ continue;
+ }
+
+ MinecraftFormat format = MinecraftFormat.fromChar(code);
+ if (format != null) {
+ result.append(format.getAnsiCode());
+ inFormat = true;
+ i++; // 跳过 format code
+ continue;
+ }
+ }
+ result.append(c);
+ }
+
+ // 最后重置所有样式
+ if (inColor || inFormat) {
+ result.append(MinecraftColors.RESET.getAnsiCode());
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * 将 Minecraft 的 § 颜色代码转换为 HTML 标签
+ * 用于在 Swing GUI 中显示颜色
+ */
+ public static String toHtml(String text) {
+ if (text == null) return "";
+
+ StringBuilder html = new StringBuilder("
");
+ int pos = 0;
+ boolean bold = false, italic = false, underline = false, strikethrough = false;
+ String currentColor = "white";
+
+ // 推荐的清晰颜色(避免 Windows 上 #FFFF55 发白)
+ String[] colors = {
+ "#222222", "#0000CC", "#00CC00", "#00CCCC",
+ "#CC0000", "#CC00CC", "#CC7700", "#777777",
+ "#555555", "#3366FF", "#55FF55", "#00FFFF",
+ "#FF5555", "#FF33CC", "#FFBB00", "#FFFFFF"
+ };
+
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (c == '§' && i + 1 < text.length()) {
+ // 添加普通文本
+ if (pos < i) {
+ html.append(escapeHtml(text.substring(pos, i)));
+ }
+
+ char code = Character.toLowerCase(text.charAt(i + 1));
+ if (code >= '0' && code <= 'f') {
+ int idx = code <= '9' ? code - '0' : code - 'a' + 10;
+ currentColor = colors[idx];
+ html.append(String.format("", currentColor));
+ } else {
+ switch (code) {
+ case 'l': // bold
+ html.append("");
+ bold = true;
+ break;
+ case 'o': // italic
+ html.append("");
+ italic = true;
+ break;
+ case 'n': // underline
+ html.append("");
+ underline = true;
+ break;
+ case 'm': // strikethrough
+ html.append("");
+ strikethrough = true;
+ break;
+ case 'r': // reset
+ if (bold) html.append("");
+ if (italic) html.append("");
+ if (underline) html.append("");
+ if (strikethrough) html.append("");
+ html.append("");
+ bold = italic = underline = strikethrough = false;
+ currentColor = "white";
+ break;
+ }
+ }
+ pos = i + 2;
+ i++; // 跳过 code
+ }
+ }
+
+ // 添加最后一段文本
+ if (pos < text.length()) {
+ html.append(escapeHtml(text.substring(pos)));
+ }
+
+ // 闭合标签
+ if (bold) html.append("");
+ if (italic) html.append("");
+ if (underline) html.append("");
+ if (strikethrough) html.append("");
+ if (!currentColor.equals("white")) html.append("");
+
+ html.append("");
+ return html.toString();
+ }
+
+ private static String escapeHtml(String s) {
+ return s.replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("'", "'");
+ }
+}
diff --git a/src/main/java/low/citory/util/MinecraftColors.java b/src/main/java/low/citory/util/MinecraftColors.java
index 328e56e..d7a0030 100644
--- a/src/main/java/low/citory/util/MinecraftColors.java
+++ b/src/main/java/low/citory/util/MinecraftColors.java
@@ -1,23 +1,23 @@
package low.citory.util;
-enum MinecraftColors {
- V_0("\u001B[30m"), // BLACK
- V_1("\u001B[34m"), // DARK BLUE
- V_2("\u001B[32m"), // DARK GREEN
- V_3("\u001B[36m"), // DARK AQUA
- V_4("\u001B[31m"), // DARK RED
- V_5("\u001B[35m"), // DARK PURPLE
- V_6("\u001B[33m"), // GOLD
- V_7("\u001B[37m"), // GRAY
- V_8("\u001B[90m"), // DARK GRAY
- V_9("\u001B[94m"), // BLUE
- V_A("\u001B[92m"), // GREEN
- V_B("\u001B[96m"), // AQUA
- V_C("\u001B[91m"), // RED
- V_D("\u001B[95m"), // PURPLE
- V_E("\u001B[93m"), // YELLOW
- V_F("\u001B[97m"), // WHITE
- V_R("\u001B[0m"); // RESET
+public enum MinecraftColors {
+ BLACK ("\u001B[30m"), // §0
+ DARK_BLUE ("\u001B[34m"), // §1
+ DARK_GREEN ("\u001B[32m"), // §2
+ DARK_AQUA ("\u001B[36m"), // §3
+ DARK_RED ("\u001B[31m"), // §4
+ DARK_PURPLE("\u001B[35m"), // §5
+ GOLD ("\u001B[33m"), // §6
+ GRAY ("\u001B[37m"), // §7
+ DARK_GRAY ("\u001B[90m"), // §8
+ BLUE ("\u001B[94m"), // §9
+ GREEN ("\u001B[92m"), // §a
+ AQUA ("\u001B[96m"), // §b
+ RED ("\u001B[91m"), // §c
+ LIGHT_PURPLE("\u001B[95m"), // §d
+ YELLOW ("\u001B[93m"), // §e
+ WHITE ("\u001B[97m"), // §f
+ RESET ("\u001B[0m"); // §r
private final String ansiCode;
@@ -33,4 +33,27 @@ public String getAnsiCode() {
public String toString() {
return ansiCode;
}
+
+ public static MinecraftColors fromChar(char code) {
+ return switch (Character.toLowerCase(code)) {
+ case '0' -> BLACK;
+ case '1' -> DARK_BLUE;
+ case '2' -> DARK_GREEN;
+ case '3' -> DARK_AQUA;
+ case '4' -> DARK_RED;
+ case '5' -> DARK_PURPLE;
+ case '6' -> GOLD;
+ case '7' -> GRAY;
+ case '8' -> DARK_GRAY;
+ case '9' -> BLUE;
+ case 'a' -> GREEN;
+ case 'b' -> AQUA;
+ case 'c' -> RED;
+ case 'd' -> LIGHT_PURPLE;
+ case 'e' -> YELLOW;
+ case 'f' -> WHITE;
+ case 'r' -> RESET;
+ default -> null;
+ };
+ }
}
diff --git a/src/main/java/low/citory/util/MinecraftFormat.java b/src/main/java/low/citory/util/MinecraftFormat.java
new file mode 100644
index 0000000..4595c3d
--- /dev/null
+++ b/src/main/java/low/citory/util/MinecraftFormat.java
@@ -0,0 +1,30 @@
+package low.citory.util;
+
+public enum MinecraftFormat {
+ BOLD ("\u001B[1m"), // §l
+ ITALIC ("\u001B[3m"), // §o
+ UNDERLINE ("\u001B[4m"), // §n
+ STRIKETHROUGH ("\u001B[9m"), // §m
+ RESET ("\u001B[0m"); // §r
+
+ private final String ansiCode;
+
+ MinecraftFormat(String ansiCode) {
+ this.ansiCode = ansiCode;
+ }
+
+ public String getAnsiCode() {
+ return ansiCode;
+ }
+
+ public static MinecraftFormat fromChar(char code) {
+ return switch (Character.toLowerCase(code)) {
+ case 'l' -> BOLD;
+ case 'o' -> ITALIC;
+ case 'n' -> UNDERLINE;
+ case 'm' -> STRIKETHROUGH;
+ case 'r' -> RESET;
+ default -> null;
+ };
+ }
+}
diff --git a/src/main/java/low/citory/util/TextColorizer.java b/src/main/java/low/citory/util/TextColorizer.java
index a097643..df0c342 100644
--- a/src/main/java/low/citory/util/TextColorizer.java
+++ b/src/main/java/low/citory/util/TextColorizer.java
@@ -1,30 +1,31 @@
package low.citory.util;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
public final class TextColorizer {
- private static final Pattern PATTERN = Pattern.compile("§([0-9a-fk-or])", Pattern.CASE_INSENSITIVE);
-
- public static String convertToAnsi(String string) {
- Matcher matcher = PATTERN.matcher(string+"§r");
- StringBuilder finallyString = new StringBuilder();
+ private static final String COLOR_CODE_PATTERN = "§[0-9a-fk-or]";
- while (matcher.find()) {
- char color = matcher.group(1).charAt(0);
- String colorCode = "V_"+Character.toUpperCase(color);
- String code = Optional.of(MinecraftColors.valueOf(colorCode)).
- map(MinecraftColors::getAnsiCode).orElse("");
+ /**
+ * 移除所有 Minecraft 颜色和格式代码 (§r, §a, §l 等)
+ */
+ public static String stripFormatting(String text) {
+ return stripColorCodes(text);
+ }
- matcher.appendReplacement(finallyString, Matcher.quoteReplacement(code));
- }
- matcher.appendTail(finallyString);
- return finallyString.toString();
+ /**
+ * 移除所有 Minecraft 颜色和格式代码
+ * 此方法可在类内部或外部调用
+ */
+ public static String stripColorCodes(String text) {
+ if (text == null) return null;
+ return text.replaceAll(COLOR_CODE_PATTERN, "");
}
- public static String stripFormatting(String text) {
- return text.replaceAll("§[0-9a-fk-or]", "");
+ /**
+ * 将 Minecraft 的 § 颜色代码转换为 ANSI 转义序列,用于终端着色
+ * 使用 MinecraftANSI 工具类
+ */
+ public static String convertToAnsi(String string) {
+ if (string == null) return null;
+ return MinecraftANSI.toAnsi(string);
}
}
From 33a1b68f2bcaf3676a1a17e958dd7f4323d481f5 Mon Sep 17 00:00:00 2001
From: ShulkerSakura <2531493755@qq.com>
Date: Wed, 3 Sep 2025 17:55:59 +0800
Subject: [PATCH 2/2] Fixed ModernPing & New MOTD Analysis
---
src/main/java/low/citory/ExampleUsage.java | 1 +
src/main/java/low/citory/MinecraftPinger.java | 21 +----
.../java/low/citory/util/AbstractPinger.java | 4 +-
src/main/java/low/citory/util/JsonToANSI.java | 57 ++++++++++++
.../java/low/citory/util/MinecraftANSI.java | 91 -------------------
src/main/java/low/citory/util/PacketUtil.java | 10 +-
.../java/low/citory/versions/ModernPing.java | 15 ++-
7 files changed, 82 insertions(+), 117 deletions(-)
create mode 100644 src/main/java/low/citory/util/JsonToANSI.java
diff --git a/src/main/java/low/citory/ExampleUsage.java b/src/main/java/low/citory/ExampleUsage.java
index 7077349..9f03075 100644
--- a/src/main/java/low/citory/ExampleUsage.java
+++ b/src/main/java/low/citory/ExampleUsage.java
@@ -28,6 +28,7 @@ public static void main(String[] args) {
// Colors work only with "System.out.println()"
// Idk how to fix colors with log4j
LOGGER.info("Server MOTD: {}", server.getAnsiMotd());
+ LOGGER.info("Server Raw MOTD: {}", server.getRawMotd());
LOGGER.info("Players online: {}/{}", server.getPlayersOnline(), server.getMaxPlayers());
LOGGER.info("Server ping: {}ms", server.getServerPing());
}
diff --git a/src/main/java/low/citory/MinecraftPinger.java b/src/main/java/low/citory/MinecraftPinger.java
index 5e5b51f..b246ebe 100644
--- a/src/main/java/low/citory/MinecraftPinger.java
+++ b/src/main/java/low/citory/MinecraftPinger.java
@@ -8,8 +8,7 @@ public final class MinecraftPinger {
private boolean serverStatus;
private String serverVersion;
private int protocolVersion;
- private String serverMOTD;
- private String strippedMOTD;
+ private String rawMOTD;
private String ansiMOTD;
private int playersOnline;
private int maxPlayers;
@@ -44,11 +43,7 @@ public void setProtocol(int protocol) {
}
public void setRawMotd(String rawMotd) {
- this.serverMOTD = rawMotd;
- }
-
- public void setStrippedMotd(String strippedMotd) {
- this.strippedMOTD = strippedMotd;
+ this.rawMOTD = rawMotd;
}
public void setAnsiMotd(String ansiMotd) {
@@ -97,16 +92,8 @@ public int getProtocolVersion() {
* Get server motd
* @return string
*/
- public String getMotd() {
- return this.serverMOTD;
- }
-
- /**
- * Get stripped motd
- * @return string
- */
- public String getStrippedMotd() {
- return this.strippedMOTD;
+ public String getRawMotd() {
+ return this.rawMOTD;
}
/**
diff --git a/src/main/java/low/citory/util/AbstractPinger.java b/src/main/java/low/citory/util/AbstractPinger.java
index ad34370..53d32b2 100644
--- a/src/main/java/low/citory/util/AbstractPinger.java
+++ b/src/main/java/low/citory/util/AbstractPinger.java
@@ -10,8 +10,8 @@ protected static void setServerData(
pinger.setVersion(version);
pinger.setProtocol(protocol);
pinger.setRawMotd(rawMotd);
- pinger.setStrippedMotd(TextColorizer.stripFormatting(rawMotd));
- pinger.setAnsiMotd(TextColorizer.convertToAnsi(rawMotd));
+ pinger.setAnsiMotd(MinecraftANSI.toAnsi(JsonToANSI.convert(rawMotd)));
+ //Don't know how to convert hex color into ANSI plz finish this.
pinger.setPlayersOnline(playersOnline);
pinger.setMaxPlayers(maxPlayers);
pinger.setServerPing(ping);
diff --git a/src/main/java/low/citory/util/JsonToANSI.java b/src/main/java/low/citory/util/JsonToANSI.java
new file mode 100644
index 0000000..4195277
--- /dev/null
+++ b/src/main/java/low/citory/util/JsonToANSI.java
@@ -0,0 +1,57 @@
+package low.citory.util;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class JsonToANSI {
+
+ public static String convert(String rawMotd) {
+
+ try {
+ JsonElement root = JsonParser.parseString(rawMotd);
+ StringBuilder result = new StringBuilder();
+ parseElement(root, result);
+ return result.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "A Minecraft Server";
+ }
+ }
+
+ private static void parseElement(JsonElement element, StringBuilder sb) {
+ if (element.isJsonObject()) {
+ JsonObject obj = element.getAsJsonObject();
+
+ // 添加 text 字段
+ if (obj.has("text")) {
+ String text = obj.get("text").getAsString();
+ sb.append(text);
+ }
+
+ // 递归处理 extra 数组
+ if (obj.has("extra")) {
+ JsonArray extra = obj.getAsJsonArray("extra");
+ for (JsonElement extraElement : extra) {
+ parseElement(extraElement, sb); // 递归处理每个 extra 元素
+ }
+ }
+
+ } else if (element.isJsonArray()) {
+ // 处理数组(如顶层是数组)
+ for (JsonElement item : element.getAsJsonArray()) {
+ parseElement(item, sb);
+ }
+ } else if (element.isJsonPrimitive()) {
+ // 处理原始类型:字符串
+ String value = element.getAsString();
+ if ("\n".equals(value)) {
+ sb.append("\n");
+ } else {
+ sb.append(value);
+ }
+ }
+ // 忽略 null 或其他类型
+ }
+}
diff --git a/src/main/java/low/citory/util/MinecraftANSI.java b/src/main/java/low/citory/util/MinecraftANSI.java
index 84c7a7e..22f0003 100644
--- a/src/main/java/low/citory/util/MinecraftANSI.java
+++ b/src/main/java/low/citory/util/MinecraftANSI.java
@@ -43,95 +43,4 @@ public static String toAnsi(String text) {
return result.toString();
}
-
- /**
- * 将 Minecraft 的 § 颜色代码转换为 HTML 标签
- * 用于在 Swing GUI 中显示颜色
- */
- public static String toHtml(String text) {
- if (text == null) return "";
-
- StringBuilder html = new StringBuilder("");
- int pos = 0;
- boolean bold = false, italic = false, underline = false, strikethrough = false;
- String currentColor = "white";
-
- // 推荐的清晰颜色(避免 Windows 上 #FFFF55 发白)
- String[] colors = {
- "#222222", "#0000CC", "#00CC00", "#00CCCC",
- "#CC0000", "#CC00CC", "#CC7700", "#777777",
- "#555555", "#3366FF", "#55FF55", "#00FFFF",
- "#FF5555", "#FF33CC", "#FFBB00", "#FFFFFF"
- };
-
- for (int i = 0; i < text.length(); i++) {
- char c = text.charAt(i);
- if (c == '§' && i + 1 < text.length()) {
- // 添加普通文本
- if (pos < i) {
- html.append(escapeHtml(text.substring(pos, i)));
- }
-
- char code = Character.toLowerCase(text.charAt(i + 1));
- if (code >= '0' && code <= 'f') {
- int idx = code <= '9' ? code - '0' : code - 'a' + 10;
- currentColor = colors[idx];
- html.append(String.format("", currentColor));
- } else {
- switch (code) {
- case 'l': // bold
- html.append("");
- bold = true;
- break;
- case 'o': // italic
- html.append("");
- italic = true;
- break;
- case 'n': // underline
- html.append("");
- underline = true;
- break;
- case 'm': // strikethrough
- html.append("");
- strikethrough = true;
- break;
- case 'r': // reset
- if (bold) html.append("");
- if (italic) html.append("");
- if (underline) html.append("");
- if (strikethrough) html.append("");
- html.append("");
- bold = italic = underline = strikethrough = false;
- currentColor = "white";
- break;
- }
- }
- pos = i + 2;
- i++; // 跳过 code
- }
- }
-
- // 添加最后一段文本
- if (pos < text.length()) {
- html.append(escapeHtml(text.substring(pos)));
- }
-
- // 闭合标签
- if (bold) html.append("");
- if (italic) html.append("");
- if (underline) html.append("");
- if (strikethrough) html.append("");
- if (!currentColor.equals("white")) html.append("");
-
- html.append("");
- return html.toString();
- }
-
- private static String escapeHtml(String s) {
- return s.replace("&", "&")
- .replace("<", "<")
- .replace(">", ">")
- .replace("\"", """)
- .replace("'", "'");
- }
}
diff --git a/src/main/java/low/citory/util/PacketUtil.java b/src/main/java/low/citory/util/PacketUtil.java
index 42b6dcd..95eaa8e 100644
--- a/src/main/java/low/citory/util/PacketUtil.java
+++ b/src/main/java/low/citory/util/PacketUtil.java
@@ -13,12 +13,14 @@
public final class PacketUtil {
// Writers
- public static void writeVarInt(DataOutputStream outputStream, int value) throws IOException {
+ public static void writeVarInt(DataOutputStream out, int value) throws IOException {
while (true) {
- if ((value & 0xFFFFFF80) == 0) outputStream.writeByte(value);
- if ((value & 0xFFFFFF80) == 0) return;
+ if ((value & 0xFFFFFF80) == 0) {
+ out.writeByte(value);
+ return; // 👈 必须 return!
+ }
- outputStream.writeByte(value & 0x7F | 0x80);
+ out.writeByte((value & 0x7F) | 0x80);
value >>>= 7;
}
}
diff --git a/src/main/java/low/citory/versions/ModernPing.java b/src/main/java/low/citory/versions/ModernPing.java
index 2f5e7a4..68805ee 100644
--- a/src/main/java/low/citory/versions/ModernPing.java
+++ b/src/main/java/low/citory/versions/ModernPing.java
@@ -32,7 +32,7 @@ public static boolean pingServer(MinecraftPinger pinger, String serverIP, int se
DataOutputStream handshake = new DataOutputStream(rawHandshake);
handshake.writeByte(0x00);
- PacketUtil.writeVarInt(handshake, -1);
+ PacketUtil.writeVarInt(handshake, 765);
PacketUtil.writeString(handshake, serverIP);
handshake.writeShort(serverPORT);
PacketUtil.writeVarInt(handshake, 1);
@@ -40,10 +40,18 @@ public static boolean pingServer(MinecraftPinger pinger, String serverIP, int se
PacketUtil.writeVarInt(outputStream, rawHandshake.size());
outputStream.write(rawHandshake.toByteArray());
- PacketUtil.writeVarInt(outputStream, 1);
- PacketUtil.writeVarInt(outputStream, 0);
+ ByteArrayOutputStream statusReq = new ByteArrayOutputStream();
+ DataOutputStream tmp = new DataOutputStream(statusReq);
+ PacketUtil.writeVarInt(tmp, 0x00); // Status Request packet ID
+ byte[] reqBytes = statusReq.toByteArray();
+
+ PacketUtil.writeVarInt(outputStream, reqBytes.length);
+ outputStream.write(reqBytes);
outputStream.flush();
+ // Step 1: 读取整个包的长度(VarInt)
+ int packetLength = PacketUtil.readVarInt(inputStream);
+ // Step 2: 读取 Packet ID
int packetId = PacketUtil.readVarInt(inputStream);
if (packetId != 0x00) return false;
@@ -61,6 +69,7 @@ private static void parseJsonResponse(MinecraftPinger pinger, String data, long
try {
Gson gson = new Gson();
JsonObject json = gson.fromJson(data, JsonObject.class);
+ //System.out.println(json.toString());
JsonObject versionData = json.getAsJsonObject("version");
String version = versionData.get("name").getAsString();